architecture2025-03-28·8 min·137/348

Vercel Blob 스토리지 활용

Vercel Blob 스토리지를 활용하여 파일 저장 및 관리하는 방법을 알아봅니다.

Vercel Blob 스토리지 활용

Introduction

Vercel Blob 스토리지는 서버리스 환경에서 파일을 저장하고 관리하는 솔루션입니다. Next.js 애플리케이션에서 이미지, 문서 등 다양한 파일을 효율적으로 관리하는 방법을 알아보겠습니다.

Environment

  • Vercel 프로젝트 (Next.js 14+)
  • Vercel Blob Storage
  • Node.js 18+
  • TypeScript

Problem

서버리스 환경에서 파일 저장 시 발생하는 문제:

// 이 코드는 Vercel에서 작동하지 않습니다
const fs = require('fs');

app.post('/upload', (req, res) => {
  const file = req.files.file;
  
  // 로컬 파일 시스템에 저장 불가
  fs.writeFile(`./uploads/${file.name}`, file.data, (err) => {
    if (err) {
      return res.status(500).json({ error: 'Upload failed' });
    }
    res.json({ success: true });
  });
});

Analysis

Vercel Blob 스토리지의 특징:

  1. 서버리스 친화적: 로컬 파일 시스템 없이 파일 저장 가능
  2. 자동 확장: 파일 크기에 따라 자동으로 확장
  3. 글로벌 배포: 전 세계에서 빠른 접근 가능
  4. 비용 효율적: 사용한 만큼만 비용 발생

Solution

1. Vercel Blob 설치 및 설정

npm install @vercel/blob
// .env.local
BLOB_READ_WRITE_TOKEN=your-blob-token

2. 파일 업로드 구현

// app/api/upload/route.js
import { put } from '@vercel/blob';
import { NextResponse } from 'next/server';

export async function POST(request) {
  const formData = await request.formData();
  const file = formData.get('file');
  
  if (!file) {
    return NextResponse.json(
      { error: 'No file provided' },
      { status: 400 }
    );
  }
  
  const blob = await put(file.name, file, {
    access: 'public',
  });
  
  return NextResponse.json({
    url: blob.url,
    pathname: blob.pathname,
  });
}

3. 파일 목록 조회

// app/api/files/route.js
import { list } from '@vercel/blob';
import { NextResponse } from 'next/server';

export async function GET() {
  const { blobs } = await list();
  
  return NextResponse.json({
    files: blobs.map(blob => ({
      url: blob.url,
      pathname: blob.pathname,
      size: blob.size,
      uploadedAt: blob.uploadedAt,
    })),
  });
}

4. 파일 삭제

// app/api/delete/route.js
import { del } from '@vercel/blob';
import { NextResponse } from 'next/server';

export async function POST(request) {
  const { url } = await request.json();
  
  if (!url) {
    return NextResponse.json(
      { error: 'URL required' },
      { status: 400 }
    );
  }
  
  await del(url);
  
  return NextResponse.json({ success: true });
}

5. 클라이언트 컴포넌트에서 사용

// components/FileUpload.jsx
'use client';

import { useState } from 'react';
import { upload } from '@vercel/blob/client';

export default function FileUpload() {
  const [uploading, setUploading] = useState(false);
  const [fileUrl, setFileUrl] = useState(null);
  
  const handleUpload = async (e) => {
    const file = e.target.files[0];
    if (!file) return;
    
    setUploading(true);
    
    try {
      const newBlob = await upload(file.name, file, {
        access: 'public',
        handleUploadUrl: '/api/upload',
      });
      
      setFileUrl(newBlob.url);
    } catch (error) {
      console.error('Upload failed:', error);
    } finally {
      setUploading(false);
    }
  };
  
  return (
    
{uploading &&

Uploading...

} {fileUrl && (

File uploaded:

{fileUrl}
)}
); }

6. 이미지 최적화와 결합

// lib/image-storage.js
import { put } from '@vercel/blob';
import sharp from 'sharp';

export async function uploadOptimizedImage(file) {
  const buffer = Buffer.from(await file.arrayBuffer());
  
  // 이미지 최적화
  const optimized = await sharp(buffer)
    .resize(1920, 1080, {
      fit: 'inside',
      withoutEnlargement: true,
    })
    .webp({ quality: 80 })
    .toBuffer();
  
  // Blob에 업로드
  const blob = await put(`images/${Date.now()}.webp`, optimized, {
    access: 'public',
    contentType: 'image/webp',
  });
  
  return blob.url;
}

7. 메타데이터 관리

// lib/blob-metadata.js
import { list } from '@vercel/blob';

export async function getFilesWithMetadata(prefix = '') {
  const { blobs } = await list({ prefix });
  
  return blobs.map(blob => ({
    ...blob,
    metadata: blob.customMetadata || {},
  }));
}

export async function updateMetadata(url, metadata) {
  // Vercel Blob은 메타데이터 업데이트를 지원하지 않으므로
  // 별도의 데이터베이스에 메타데이터를 저장하는 방식 사용
  // 예시: Prisma나 Redis에 메타데이터 저장
}

8. 에러 핸들링

// lib/blob-utils.js
import { put, list, del } from '@vercel/blob';

export async function safeUpload(name, file, options = {}) {
  try {
    const blob = await put(name, file, {
      access: 'public',
      ...options,
    });
    
    return { success: true, blob };
  } catch (error) {
    console.error('Upload failed:', error);
    return { success: false, error: error.message };
  }
}

export async function safeList(options = {}) {
  try {
    const result = await list(options);
    return { success: true, ...result };
  } catch (error) {
    console.error('List failed:', error);
    return { success: false, error: error.message };
  }
}

export async function safeDelete(url) {
  try {
    await del(url);
    return { success: true };
  } catch (error) {
    console.error('Delete failed:', error);
    return { success: false, error: error.message };
  }
}

Lessons Learned

  1. 보안: 파일 업로드 시 적절한 검증과 보안 처리가 필요합니다.
  2. 비용 관리: Blob 스토리지 사용량을 모니터링하여 비용을 관리해야 합니다.
  3. 이미지 최적화: 업로드 시 이미지를 최적화하면 저장 공간과 대역폭을 절약할 수 있습니다.
  4. 백업 전략: 중요 파일은 백업 전략을 수립해야 합니다.
  5. CDN 활용: Blob 스토리지는 CDN을 통해 빠른 파일 전달을 제공합니다.

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.