Vercelのファイルアップロード設定と上限|Vercel Blob・API制限・回避策
Vercel にデプロイしたアプリでファイルアップロードを実装する際、Serverless Function のリクエストボディには上限があります。デフォルトで 4.5MB というこの制限はしばしばはまりポイントになります。本記事では Vercel Function のボディサイズ制限、vercel.json での設定方法、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 のボディサイズ上限は インフラレベルの制限 であり、アプリケーションコードでの設定変更では回避できない点です。4.5MB を超えるファイルをアップロードするには、アーキテクチャ的な回避策が必要です。
vercel.json での設定
vercel.json では Function の実行時間(maxDuration)やメモリ(memory)は設定できますが、ボディサイズ上限の変更はできません。ただし Next.js の Pages Router の場合は responseLimit など一部の設定が有効です。
// 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 が提供するオブジェクトストレージサービスです。@vercel/blob パッケージを使うと、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 を経由しないため容量制限なし
この記事で使えるテストファイル(無料)
- → PNG テスト画像(1MB) — Vercel Blob へのアップロード動作確認に
- → PDF テストファイル(1MB) — 4.5MB 制限以内のファイルアップロードテストに
- → ZIP テストファイル(1MB) — Presigned URL 経由の S3 直接アップロードテストに