Vercel Blob 시작하기: Next.js에서 파일 스토리지 연동하기




들어가며

웹 서비스에서 이미지, 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 대시보드에서 설정

  1. Vercel 대시보드에 접속
  2. 프로젝트 선택 → Storage 탭 클릭
  3. Connect DatabaseBlob 선택
  4. Store 이름 입력 (예: my-blob-store)
  5. 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: 대시보드에서 수동 업로드

가장 간단한 방법입니다.

  1. Vercel 대시보드 → Storage → Blob Store 선택
  2. Upload 버튼 클릭
  3. 파일 드래그 앤 드롭

폴더 구조도 지원합니다. 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 비교
  • 대용량 파일 업로드 (멀티파트)
  • 이미지 최적화와 함께 사용하기



댓글 남기기