Configuración de carga de archivos de Vercel y límites | Vercel Blob, límites de API y soluciones
Al implementar cargas de archivos en una aplicación implementada en Vercel, existe un límite en el tamaño del cuerpo de la solicitud de Funciones sin servidor. Este límite predeterminado de 4.5MB a menudo es un obstáculo. Este artículo proporciona explicaciones detalladas sobre los límites de tamaño de cuerpo de Vercel Function, métodos de configuración en <code>vercel.json</code>, cómo usar Vercel Blob y cómo manejar archivos grandes evitando el límite (carga directa a S3).
Límite de tamaño de cuerpo de Vercel Function
Las Funciones sin servidor de Vercel (API Routes, Route Handlers, Edge Functions) tienen un límite de tamaño de solicitud. Las solicitudes que exceden este límite resultan en un error 413.
| Tipos de Function | Límite predeterminado | Valor máximo de configuración | Notas |
|---|---|---|---|
| Serverless Function(Node.js) | 4.5 MB | 4.5 MB | No se puede cambiar en vercel.json |
| Edge Function | 4 MB | 4 MB | No se puede cambiar |
| Next.js Server Actions | 1 MB | Configurable | Cambios en next.config.js |
El punto clave es que el límite de tamaño del cuerpo de la Serverless Function de Vercel es una <strong>restricción a nivel de infraestructura</strong> que no se puede eludir cambiando la configuración del código de aplicación. Cargar archivos mayores a 4.5MB requiere una solución alternativa de arquitectura.
Configuración en vercel.json
En <code>vercel.json</code>, puede configurar el tiempo de ejecución de Function (<code>maxDuration</code>) y la memoria (<code>memory</code>), pero no puede cambiar el límite de tamaño del cuerpo. Sin embargo, para Next.js Pages Router, algunas configuraciones como <code>responseLimit</code> son efectivas.
// 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 });
});
}
Cómo usar Vercel Blob
Vercel Blob es un servicio de almacenamiento de objetos proporcionado por Vercel. Con el paquete <code>@vercel/blob</code>, puedes guardar y servir archivos desde tu aplicación en Vercel sin configurar S3.
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>
);
}
Solución alternativa para archivos grandes: Carga directa a S3
Si no deseas usar Vercel Blob o prefieres usar S3 directamente, cargar directamente desde el cliente a S3 a través de una URL preconfirmada es el enfoque más confiable. Dado que Vercel Function solo genera el token, no se ve afectado por los límites de tamaño de cuerpo.
// 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 を経由しないため容量制限なし
Archivos de prueba para este artículo (gratis)
- → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Imagen PNG de Prueba (1MB)</a> — Para verificar el comportamiento de carga a Vercel Blob
- → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Archivo de prueba PDF (1MB)</a> — para probar cargas de archivo dentro del límite de 4.5MB
- → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Archivo de prueba ZIP (1MB)</a> — Para prueba de carga directa de S3 via Presigned URL
Artículos relacionados
- → <a href="/ja/blog/nextjs-file-upload/" class="text-primary-600 dark:text-primary-400 hover:underline">Guía de Implementación de Carga de Archivos en Next.js | App Router, API Route, Soporte S3</a>
- → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">Resumen de Límites de Carga de Archivos para AWS S3 y CloudFront</a>
- → <a href="/ja/blog/netlify-upload-config/" class="text-primary-600 dark:text-primary-400 hover:underline">Cómo Cargar Archivos con Netlify Functions | Límites, Large Media y Soluciones Alternas</a>