들어가며
웹 서비스에서 이미지, HTML 파일, 문서 등을 저장하고 서빙해야 할 때가 있습니다. Vercel Blob은 Vercel에서 제공하는 파일 스토리지 서비스로, Next.js 프로젝트와 매우 쉽게 연동할 수 있습니다.
이 글에서는 Vercel Blob을 설정하고, Next.js에서 파일을 조회하는 방법을 단계별로 알아보겠습니다.
Vercel Blob이란?
Vercel Blob은 파일을 저장하고 URL로 접근할 수 있게 해주는 스토리지 서비스입니다.
주요 특징
- 파일을 업로드하면 고유한 URL 생성
- 글로벌 CDN 자동 적용
- Next.js SDK 제공
- 대시보드에서 파일 관리 가능
사용 사례
- 사용자 프로필 이미지
- HTML 시각화 자료
- PDF, 문서 파일
- 정적 에셋
1단계: Blob Store 생성
Vercel 대시보드에서 설정
- Vercel 대시보드에 접속
- 프로젝트 선택 → Storage 탭 클릭
- Connect Database → Blob 선택
- Store 이름 입력 (예:
my-blob-store) - Create 클릭
생성이 완료되면 BLOB_READ_WRITE_TOKEN 환경 변수가 자동으로 프로젝트에 추가됩니다.
로컬 환경 설정
로컬에서 개발할 때는 환경 변수를 가져와야 합니다.
# Vercel CLI로 환경 변수 가져오기
vercel env pull .env.local
또는 .env.local 파일에 직접 추가:
BLOB_READ_WRITE_TOKEN=vercel_blob_xxxxxxxxxxxxx
2단계: SDK 설치
npm install @vercel/blob
3단계: 파일 업로드하기
방법 1: 대시보드에서 수동 업로드
가장 간단한 방법입니다.
- Vercel 대시보드 → Storage → Blob Store 선택
- Upload 버튼 클릭
- 파일 드래그 앤 드롭
폴더 구조도 지원합니다. visuals/main.html처럼 경로를 지정하면 폴더가 자동 생성됩니다.
방법 2: API Route로 업로드
서버에서 프로그래밍 방식으로 업로드할 수도 있습니다.
// app/api/upload/route.ts
import { put } from '@vercel/blob';
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const formData = await request.formData();
const file = formData.get('file') as File;
const blob = await put(file.name, file, {
access: 'public',
});
return NextResponse.json(blob);
}
4단계: 파일 조회하기
파일 목록 조회
// app/visuals/page.tsx
import { list } from '@vercel/blob';
export default async function VisualsPage() {
const { blobs } = await list({ prefix: 'visuals/' });
return (
<div className="grid grid-cols-3 gap-4 p-8">
{blobs.map((blob) => {
const slug = blob.pathname
.replace('visuals/', '')
.replace('.html', '');
return (
<a
key={blob.url}
href={`/visuals/${slug}`}
className="p-4 border rounded hover:bg-gray-50"
>
<h3 className="font-medium">{slug}</h3>
<p className="text-sm text-gray-500">
{new Date(blob.uploadedAt).toLocaleDateString()}
</p>
</a>
);
})}
</div>
);
}
Blob 메타데이터 구조
list() 함수가 반환하는 각 blob 객체:
{
url: "https://xxx.blob.vercel-storage.com/visuals/main.html",
pathname: "visuals/main.html",
size: 2048,
contentType: "text/html",
uploadedAt: "2025-01-15T10:30:00.000Z"
}
5단계: 개별 파일 렌더링
HTML 파일을 iframe으로 표시
// app/visuals/[slug]/page.tsx
interface PageProps {
params: { slug: string };
}
export default async function VisualPage({ params }: PageProps) {
const { slug } = params;
const blobUrl = `${process.env.BLOB_STORE_URL}/visuals/${slug}.html`;
const res = await fetch(blobUrl);
if (!res.ok) {
return <div>파일을 찾을 수 없습니다.</div>;
}
const html = await res.text();
return (
<div className="w-full h-screen">
<header className="p-4 border-b flex justify-between items-center">
<a href="/visuals" className="text-blue-600">← 목록</a>
<h1 className="font-medium">{slug}</h1>
<a href={blobUrl} target="_blank" className="text-blue-600">
새 창에서 열기
</a>
</header>
<iframe
srcDoc={html}
className="w-full h-[calc(100vh-60px)] border-0"
sandbox="allow-scripts"
/>
</div>
);
}
주의: sandbox 속성
외부 HTML을 렌더링할 때는 보안을 위해 sandbox 속성을 사용하세요.
<!-- 스크립트 허용 -->
<iframe sandbox="allow-scripts" />
<!-- 스크립트 + 폼 허용 -->
<iframe sandbox="allow-scripts allow-forms" />
<!-- 모든 제한 (가장 안전) -->
<iframe sandbox="" />
6단계: 메타데이터 관리 (선택)
기본 메타데이터(파일명, 크기, 날짜)만으로 부족하다면, 별도의 메타데이터 파일을 사용할 수 있습니다.
_meta.json 파일 생성
Blob에 visuals/_meta.json 파일을 업로드합니다:
{
"main": {
"title": "메인 페이지",
"description": "심플한 랜딩 페이지 예제",
"tags": ["landing", "simple"]
},
"chart-demo": {
"title": "차트 데모",
"description": "막대 차트 시각화",
"tags": ["chart", "data"]
}
}
메타데이터와 함께 목록 조회
// lib/visuals.ts
import { list } from '@vercel/blob';
interface VisualMeta {
title: string;
description: string;
tags: string[];
}
interface Visual {
slug: string;
url: string;
uploadedAt: string;
meta?: VisualMeta;
}
export async function getVisuals(): Promise<Visual[]> {
const { blobs } = await list({ prefix: 'visuals/' });
// 메타데이터 파일 조회
const metaBlob = blobs.find(b => b.pathname === 'visuals/_meta.json');
let metadata: Record<string, VisualMeta> = {};
if (metaBlob) {
const res = await fetch(metaBlob.url);
metadata = await res.json();
}
// HTML 파일만 필터링하고 메타데이터 병합
return blobs
.filter(blob => blob.pathname.endsWith('.html'))
.map(blob => {
const slug = blob.pathname
.replace('visuals/', '')
.replace('.html', '');
return {
slug,
url: blob.url,
uploadedAt: blob.uploadedAt,
meta: metadata[slug],
};
});
}
전체 프로젝트 구조
app/
├── visuals/
│ ├── page.tsx # 목록 페이지
│ └── [slug]/
│ └── page.tsx # 상세 페이지
├── api/
│ └── upload/
│ └── route.ts # 업로드 API (선택)
└── lib/
└── visuals.ts # 유틸 함수
Blob Store:
visuals/
├── main.html
├── chart-demo.html
├── dashboard.html
└── _meta.json # 메타데이터 (선택)
유용한 SDK 함수들
파일 삭제
import { del } from '@vercel/blob';
await del('https://xxx.blob.vercel-storage.com/visuals/old-file.html');
파일 존재 확인
import { head } from '@vercel/blob';
const blob = await head('https://xxx.blob.vercel-storage.com/visuals/main.html');
// blob이 null이면 파일 없음
파일 복사
import { copy } from '@vercel/blob';
await copy(
'https://xxx.blob.vercel-storage.com/visuals/main.html',
'visuals/main-backup.html'
);
마치며
Vercel Blob은 Next.js 프로젝트에서 파일 스토리지를 가장 쉽게 구현할 수 있는 방법입니다. 복잡한 설정 없이 몇 줄의 코드로 파일을 저장하고 서빙할 수 있습니다.
이 글에서 다룬 내용:
- Blob Store 생성 및 설정
- 파일 업로드 (수동/API)
- 파일 목록 조회 및 렌더링
- 메타데이터 관리
더 자세한 내용은 Vercel Blob 공식 문서를 참고하세요.
다음 글에서는…
- Vercel Blob vs S3 vs Cloudflare R2 비교
- 대용량 파일 업로드 (멀티파트)
- 이미지 최적화와 함께 사용하기