Vercel 파일 업로드 설정 및 제한 | Vercel Blob, API 제한, 회피 방법
Vercel에 배포된 앱에서 파일 업로드를 구현할 때, Serverless Function의 요청 본문에는 제한이 있습니다. 기본 4.5MB의 이 제한은 종종 문제가 됩니다. 본 문서는 Vercel Function 본문 크기 제한, <code>vercel.json</code>에서의 설정 방법, Vercel Blob 사용 방법, 그리고 제한을 우회하여 대용량 파일을 처리하는 방법(S3으로의 직접 업로드)을 자세히 설명합니다.
Vercel Function 본문 크기 제한
Vercel의 Serverless Function(API Routes, Route Handlers, Edge Functions)에는 요청 크기 제한이 있습니다. 이 제한을 초과하는 요청은 413 오류를 발생시킵니다.
| Function의 종류 | 기본 제한 | 최대 설정값 | 비고 |
|---|---|---|---|
| Serverless Function(Node.js) | 4.5 MB | 4.5 MB | vercel.json 에서 변경 불가 |
| Edge Function | 4 MB | 4 MB | 변경 불가 |
| Next.js Server Actions | 1 MB | 설정 가능 | next.config.js에서의 변경 |
중요한 점은 Vercel의 Serverless Function 본문 크기 제한이 <strong>인프라 수준의 제한</strong>이며 애플리케이션 코드 설정 변경으로는 우회할 수 없다는 것입니다. 4.5MB를 초과하는 파일을 업로드하려면 아키텍처 수준의 우회 전략이 필요합니다.
vercel.json 에서의 설정
<code>vercel.json</code>에서는 Function의 실행 시간(<code>maxDuration</code>)과 메모리(<code>memory</code>)를 설정할 수 있지만 본문 크기 상한은 변경할 수 없습니다. 다만 Next.js의 Pages Router의 경우 <code>responseLimit</code> 등 일부 설정이 유효합니다.
// vercel.json
{
"functions": {
"app/api/upload/route.ts": {
"maxDuration": 60,
"memory": 1024
},
"pages/api/upload.ts": {
"maxDuration": 30
}
},
"headers": [
{
"source": "/api/(.*)",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
]
}
// next.config.js(Next.js の Server Actions のボディサイズを変更)
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: {
// Server Actions のボディサイズ上限を変更(Vercel インフラの制限は超えられない)
bodySizeLimit: '4mb',
},
},
};
module.exports = nextConfig;
// Pages Router の API Route でのボディパーサー無効化
// pages/api/upload.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export const config = {
api: {
// bodyParser を無効にしてストリームとして受け取る
bodyParser: false,
// responseLimit: false, // レスポンスサイズ制限を無効化
},
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
// formidable などでマルチパートを解析
const { IncomingForm } = await import('formidable');
const form = new IncomingForm({ maxFileSize: 4 * 1024 * 1024 }); // 4MB 上限
form.parse(req, (err, fields, files) => {
if (err) {
return res.status(400).json({ error: err.message });
}
const file = Array.isArray(files.file) ? files.file[0] : files.file;
if (!file) return res.status(400).json({ error: 'ファイルが見つかりません。' });
res.json({ filename: file.originalFilename, size: file.size });
});
}
Vercel Blob 사용 방법
Vercel Blob은 Vercel이 제공하는 객체 스토리지 서비스입니다. <code>@vercel/blob</code> 패키지를 사용하면 S3 설정 없이 Vercel의 앱에서 파일을 저장하고 제공할 수 있습니다.
npm install @vercel/blob
// サーバーサイドアップロード(4.5MB 以下のファイル向け)
// app/api/upload/route.ts
import { put, del, list } from '@vercel/blob';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const form = await request.formData();
const file = form.get('file') as File;
if (!file) {
return NextResponse.json({ error: 'ファイルが見つかりません。' }, { status: 400 });
}
// Vercel Blob にアップロード
// 環境変数 BLOB_READ_WRITE_TOKEN が必要
const blob = await put(file.name, file, {
access: 'public', // 'public'(誰でも閲覧可)または 'private'
addRandomSuffix: true, // ファイル名にランダムなサフィックスを追加
contentType: file.type, // MIMEタイプを明示
});
return NextResponse.json({
url: blob.url, // 配信 URL
downloadUrl: blob.downloadUrl, // ダウンロード URL
pathname: blob.pathname, // パス名
size: blob.size, // ファイルサイズ(バイト)
contentType: blob.contentType,
});
}
// ファイル一覧の取得
export async function GET() {
const { blobs } = await list({ prefix: 'uploads/' });
return NextResponse.json(blobs);
}
// ファイルの削除
export async function DELETE(request: NextRequest) {
const { url } = await request.json();
await del(url);
return NextResponse.json({ deleted: true });
}
// クライアントアップロード(4.5MB 超のファイル向け)
// Vercel Blob がトークンを発行してクライアントから直接アップロード
// app/api/upload/route.ts
import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest): Promise<NextResponse> {
const body = (await request.json()) as HandleUploadBody;
try {
const jsonResponse = await handleUpload({
body,
request,
onBeforeGenerateToken: async (pathname, clientPayload) => {
// ここでユーザー認証やバリデーションを行う
return {
allowedContentTypes: ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'],
maximumSizeInBytes: 100 * 1024 * 1024, // 100MB
tokenPayload: JSON.stringify({ userId: 'user-123' }),
};
},
onUploadCompleted: async ({ blob, tokenPayload }) => {
// アップロード完了後の処理(DB への保存など)
const { userId } = JSON.parse(tokenPayload ?? '{}');
console.log(`ユーザー ${userId} が ${blob.url} をアップロードしました`);
// await db.files.create({ userId, url: blob.url });
},
});
return NextResponse.json(jsonResponse);
} catch (error) {
return NextResponse.json({ error: String(error) }, { status: 400 });
}
}
// クライアントコンポーネントでのクライアントアップロード
// components/BlobUpload.tsx
'use client';
import { upload } from '@vercel/blob/client';
import { useState } from 'react';
export default function BlobUpload() {
const [url, setUrl] = useState('');
const [progress, setProgress] = useState(0);
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const blob = await upload(file.name, file, {
access: 'public',
handleUploadUrl: '/api/upload', // トークン発行エンドポイント
onUploadProgress: ({ percentage }) => {
setProgress(Math.round(percentage));
},
});
setUrl(blob.url);
};
return (
<div>
<input type="file" onChange={handleUpload} />
{progress > 0 && progress < 100 && <p>{progress}% アップロード中...</p>}
{url && <a href={url}>アップロードされたファイル</a>}
</div>
);
}
대용량 파일의 우회 방법: S3로의 직접 업로드
Vercel Blob을 사용하지 않거나 S3를 직접 사용하고 싶다면, Presigned URL을 통해 클라이언트에서 S3로 직접 업로드하는 방법이 가장 확실합니다. Vercel Function은 토큰 생성만 수행하므로 본문 크기 제한의 영향을 받지 않습니다.
// app/api/presigned-url/route.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { NextRequest, NextResponse } from 'next/server';
const s3 = new S3Client({ region: process.env.AWS_REGION! });
export async function POST(request: NextRequest) {
// このリクエストはファイル本体を含まないため、Vercel の上限に引っかからない
const { filename, contentType, size } = await request.json();
const key = `uploads/${Date.now()}-${filename}`;
const command = new PutObjectCommand({
Bucket: process.env.AWS_BUCKET_NAME!,
Key: key,
ContentType: contentType,
});
const presignedUrl = await getSignedUrl(s3, command, { expiresIn: 3600 });
return NextResponse.json({ presignedUrl, key });
}
// クライアントコードでは:
// 1. /api/presigned-url に POST して URL を取得
// 2. 取得した URL に XHR で PUT リクエストを送信(進捗バー付き)
// → Vercel Function を経由しないため容量制限なし
이 기사에서 사용할 수 있는 테스트 파일 (무료)
- → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PNG 테스트 이미지 (1MB)</a> — Vercel Blob 업로드 동작 확인용
- → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PDF 테스트 파일(1MB)</a> — 4.5MB 제한 이내의 파일 업로드 테스트용
- → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">ZIP 테스트 파일 (1MB)</a> — Presigned URL을 통한 S3 직접 업로드 테스트용
관련 기사
- → <a href="/ja/blog/nextjs-file-upload/" class="text-primary-600 dark:text-primary-400 hover:underline">Next.js 파일 업로드 구현 가이드 | App Router・API Route・S3 지원</a>
- → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">AWS S3・CloudFront 파일 업로드 제한 정리</a>
- → <a href="/ja/blog/netlify-upload-config/" class="text-primary-600 dark:text-primary-400 hover:underline">Netlify의 Functions에서 파일을 업로드하는 방법|상한·Large Media·회피책</a>