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,通过预签名 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>