Supabase Storage에 동영상 강의를 폴더 구조로 업로드
Supabase Storage에 동영상 강의를 폴더 구조로 업로드하려면, 제공된 GitHub 예제(resumable-upload-uppy
)를 기반으로 Uppy 라이브러리와 TUS 프로토콜을 활용해 resumable upload를 구현할 수 있습니다. 아래는 이를 수행하는 단계별 가이드입니다. 동영상 파일과 같은 대용량 파일을 안정적으로 업로드하기 위해 TUS 프로토콜을 사용하는 것이 적합하며, 폴더 구조를 지정하여 파일을 체계적으로 저장할 수 있습니다.
1. 프로젝트 설정 및 준비
Supabase Storage에 파일을 업로드하려면 Supabase 프로젝트와 Storage 버킷이 설정되어 있어야 합니다. 또한, Uppy와 TUS를 사용하기 위해 필요한 환경을 준비합니다.
(1) Supabase 설정
- Supabase 프로젝트 생성: Supabase 대시보드에서 프로젝트를 생성하고, 프로젝트 ID와 Anon Key를 확인합니다.
- Storage 버킷 생성: Supabase 대시보드에서 Storage 탭으로 이동해 새로운 버킷을 생성합니다(예:
lectures
). - 업로드 정책 설정: 동영상 업로드를 허용하려면 적절한 정책을 설정해야 합니다. 예를 들어, 아래와 같은 SQL 정책을 추가합니다:
이 정책은CREATE POLICY "allow uploads" ON storage.objects FOR INSERT TO public WITH CHECK (bucket_id = 'lectures');
lectures
버킷에 모든 사용자가 파일을 업로드할 수 있도록 허용합니다. 필요에 따라 인증된 사용자만 업로드하도록 제한할 수 있습니다.
(2) 프로젝트 환경 설정
- Node.js 프로젝트 생성: 새로운 프로젝트 디렉토리를 만들고 Node.js 환경을 설정합니다.
- 필요한 패키지 설치:
또는, 예제처럼 CDN을 통해 Uppy를 사용할 수도 있습니다.npm install @uppy/core @uppy/react @uppy/tus @uppy/dashboard
2. Uppy와 TUS를 사용한 업로드 코드 작성
아래는 Supabase의 예제 코드를 기반으로 동영상 강의를 폴더 구조로 업로드하는 React 기반 코드입니다. 동영상 파일을 lectures/course1/videos/
와 같은 폴더 구조로 저장하도록 설정합니다.
(1) 기본 코드 구조
index.html
또는 React 컴포넌트에서 Uppy를 설정합니다. 아래는 React 컴포넌트 예제입니다:
// FileUploader.jsx
"use client";
import React, { useState, useEffect } from "react";
import Uppy from "@uppy/core";
import { Dashboard } from "@uppy/react";
import Tus from "@uppy/tus";
import "@uppy/core/dist/style.min.css";
import "@uppy/dashboard/dist/style.min.css";
const SUPABASE_ANON_KEY = "your-anon-key"; // Supabase Anon Key
const SUPABASE_PROJECT_ID = "your-project-id"; // Supabase Project ID
const STORAGE_BUCKET = "lectures"; // Supabase Storage 버킷 이름
const BEARER_TOKEN = "your-bearer-token"; // 인증 토큰 (필요 시)
const FOLDER_PATH = "course1/videos"; // 업로드할 폴더 경로
const supabaseStorageURL = `https://${SUPABASE_PROJECT_ID}.supabase.co/storage/v1/upload/resumable`;
export default function FileUploader() {
const [uppy, setUppy] = useState(() =>
new Uppy({
autoProceed: false, // 수동으로 업로드 시작
})
.use(Dashboard, {
inline: true,
target: "#drag-drop-area",
showProgressDetails: true,
})
.use(Tus, {
endpoint: supabaseStorageURL,
headers: {
authorization: `Bearer ${BEARER_TOKEN}`,
apikey: SUPABASE_ANON_KEY,
},
uploadDataDuringCreation: true,
chunkSize: 6 * 1024 * 1024, // 6MB 청크 크기 (Supabase 요구사항)
allowedMetaFields: ["bucketName", "objectName", "contentType", "cacheControl"],
onError: (error) => {
console.error("Upload failed:", error);
},
})
);
// 파일이 추가될 때 메타데이터 설정
uppy.on("file-added", (file) => {
file.meta = {
...file.meta,
bucketName: STORAGE_BUCKET,
objectName: FOLDER_PATH ? `${FOLDER_PATH}/${file.name}` : file.name, // 폴더 구조 지정
contentType: file.type, // 동영상 타입 (예: video/mp4)
cacheControl: "3600",
};
});
// 업로드 완료 시 결과 처리
uppy.on("complete", (result) => {
console.log("Upload complete:", result.successful);
if (result.failed.length > 0) {
console.error("Failed uploads:", result.failed);
}
});
// 컴포넌트 언마운트 시 Uppy 정리
useEffect(() => {
return () => uppy.close();
}, [uppy]);
return (
<div>
<h1>Upload Video Lectures</h1>
<div id="drag-drop-area"></div>
</div>
);
}
(2) 코드 설명
- Supabase 설정값:
SUPABASE_ANON_KEY
,SUPABASE_PROJECT_ID
,STORAGE_BUCKET
,BEARER_TOKEN
은 Supabase 대시보드에서 확인한 값으로 교체합니다.BEARER_TOKEN
은 인증된 사용자의 경우supabase.auth.getSession()
에서 얻은access_token
을 사용할 수 있습니다. - 폴더 구조:
FOLDER_PATH
를 통해 동영상이 저장될 경로를 지정합니다. 예를 들어,course1/videos/lecture1.mp4
와 같이 저장됩니다. - Uppy 설정:
Dashboard
: 파일 선택 및 업로드 진행 상황을 보여주는 UI 컴포넌트입니다.Tus
: TUS 프로토콜을 사용해 대용량 파일을 청크 단위로 업로드합니다.chunkSize
: Supabase는 6MB로 설정해야 합니다.allowedMetaFields
: Supabase Storage에 필요한 메타데이터를 전달합니다.
- 파일 메타데이터:
file-added
이벤트에서 파일의 메타데이터를 설정해 폴더 경로와 콘텐츠 타입(예:video/mp4
)을 지정합니다.
(3) HTML 기반 예제 (CDN 사용)
React 대신 HTML 파일을 사용하려면, Supabase 예제의 index.html
을 수정합니다:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resumable Upload Supabase + UppyJS</title>
<link href="https://releases.transloadit.com/uppy/v3.6.1/uppy.min.css" rel="stylesheet" />
<style>
body {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
#drag-drop-area {
margin-top: 40px;
}
</style>
</head>
<body>
<h1>Upload Video Lectures</h1>
<div id="drag-drop-area"></div>
<script type="module">
import { Uppy, Dashboard, Tus } from "https://releases.transloadit.com/uppy/v3.6.1/uppy.min.mjs";
const SUPABASE_ANON_KEY = "your-anon-key";
const SUPABASE_PROJECT_ID = "your-project-id";
const STORAGE_BUCKET = "lectures";
const BEARER_TOKEN = "your-bearer-token";
const FOLDER_PATH = "course1/videos";
const supabaseStorageURL = `https://${SUPABASE_PROJECT_ID}.supabase.co/storage/v1/upload/resumable`;
const uppy = new Uppy()
.use(Dashboard, {
inline: true,
target: "#drag-drop-area",
showProgressDetails: true,
})
.use(Tus, {
endpoint: supabaseStorageURL,
headers: {
authorization: `Bearer ${BEARER_TOKEN}`,
apikey: SUPABASE_ANON_KEY,
},
uploadDataDuringCreation: true,
chunkSize: 6 * 1024 * 1024,
allowedMetaFields: ["bucketName", "objectName", "contentType", "cacheControl"],
onError: (error) => {
console.log("Failed because: " + error);
},
});
uppy.on("file-added", (file) => {
file.meta = {
...file.meta,
bucketName: STORAGE_BUCKET,
objectName: FOLDER_PATH ? `${FOLDER_PATH}/${file.name}` : file.name,
contentType: file.type,
cacheControl: "3600",
};
});
uppy.on("complete", (result) => {
console.log("Upload complete:", result.successful);
});
</script>
</body>
</html>
3. 로컬에서 테스트
- 로컬 서버 실행:
- HTML 파일을 사용하는 경우, 간단한 HTTP 서버를 실행합니다:
npx http-server
- React 프로젝트라면,
npm run dev
로 Next.js 또는 Vite 서버를 실행합니다.
- HTML 파일을 사용하는 경우, 간단한 HTTP 서버를 실행합니다:
- 파일 업로드 테스트:
- 브라우저에서 Uppy 대시보드를 열고 동영상 파일을 선택합니다.
- 업로드 진행 상황을 확인하고, Supabase 대시보드의 Storage 탭에서
lectures/course1/videos/
경로에 파일이 저장되었는지 확인합니다.
4. 중요 고려사항
- 파일 크기 제한: Supabase의 유료 플랜에서는 최대 50GB까지 업로드 가능합니다. 무료 플랜은 5GB로 제한됩니다.
- 파일 이름 유효성: Supabase는 파일 이름에 대해 엄격한 유효성 검사를 수행합니다. 특수 문자가 포함된 파일 이름은 오류를 유발할 수 있으므로, UUID나 타임스탬프를 사용해 고유한 파일 이름을 생성하는 것이 좋습니다. 예:
파일 추가 시:const generateFileName = (originalName) => { const uuid = require("uuid").v4(); const extension = originalName.split(".").pop(); return `${uuid}.${extension}`; };
uppy.on("file-added", (file) => { const newFileName = generateFileName(file.name); file.meta = { ...file.meta, bucketName: STORAGE_BUCKET, objectName: `${FOLDER_PATH}/${newFileName}`, contentType: file.type, }; });
- 오류 처리: 업로드 중 404, 410, 또는 409 오류가 발생할 수 있습니다. 예를 들어:
- TLS 문제: 로컬 개발 환경에서 업로드가 6MB에서 멈추는 경우,
supabase/config.toml
에서 TLS를 비활성화하거나 올바른 인증서를 설정하세요. - CDN 지연: 동일한 경로에 파일을 덮어쓰면 CDN 전파로 인해 최신 파일이 즉시 반영되지 않을 수 있습니다. 새 경로에 업로드하는 것을 권장합니다.
5. 추가 팁
- 인증 통합: 인증된 사용자의 업로드를 위해
supabase.auth.getSession()
을 사용해 동적으로BEARER_TOKEN
을 설정하세요:const { data: { session } } = await supabase.auth.getSession(); const BEARER_TOKEN = session.access_token;
- 업로드 진행률 표시: Uppy의
onProgress
이벤트를 활용해 사용자에게 업로드 진행 상황을 표시할 수 있습니다:uppy.on("progress", (progress) => { console.log(`Upload progress: ${progress}%`); });
- 대용량 파일 최적화: 동영상 파일은 크기가 클 수 있으므로, 항상
https://${SUPABASE_PROJECT_ID}.storage.supabase.co
와 같은 직접 스토리지 호스트를 사용해 성능을 최적화하세요.
6. 결론
위 코드를 사용하면 Supabase Storage에 동영상 강의를 lectures/course1/videos/
와 같은 폴더 구조로 업로드할 수 있습니다. Uppy와 TUS 프로토콜을 활용해 대용량 파일 업로드를 안정적으로 처리하며, 파일 이름 충돌이나 CDN 지연 문제를 피하려면 고유한 파일 이름과 새 경로를 사용하는 것이 좋습니다. 추가적인 오류가 발생하면 Supabase 문서()와 GitHub 이슈(,)를 참고해 디버깅하세요.
필요한 경우, 특정 오류나 추가 요구사항을 알려주시면 더 구체적으로 도와드리겠습니다!