Saltar al contenido

Configuración de carga de archivos de Vercel y límites | Vercel Blob, límites de API y soluciones

Categoría: Vercel / Configuración de despliegue

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 vercel.json, cómo usar Vercel Blob y cómo manejar archivos grandes evitando el límite (carga directa a S3).

Serverless Function 4.5 MB request body 10s ~ 900s timeout Edge Function 4 MB request body 25s timeout / streaming Vercel Blob 5 GB via client upload bypasses fn limit
Diagrama: límites de tamaño en funciones Vercel y rodeo con Blob

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 restricción a nivel de infraestructura 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 vercel.json, puede configurar el tiempo de ejecución de Function (maxDuration) y la memoria (memory), pero no puede cambiar el límite de tamaño del cuerpo. Sin embargo, para Next.js Pages Router, algunas configuraciones como responseLimit 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 @vercel/blob, 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 を経由しないため容量制限なし

❓ Preguntas frecuentes

¿Hay alguna forma de subir el límite de carga de Vercel?
El límite de 4,5 MB en las Serverless Functions de Vercel se aplica a nivel de infraestructura y no se puede cambiar desde vercel.json ni desde el código. Para archivos mayores, usa la subida de cliente de Vercel Blob o una URL prefirmada de S3 para que el cliente suba directamente. En ambos casos el archivo no pasa por la Function, así que el límite no aplica.
¿Debo usar Vercel Blob o AWS S3?
Vercel Blob se integra con Vercel casi sin configuración, pero el almacenamiento cuesta más que S3. Encaja en proyectos pequeños o medianos y en equipos que quieren quedarse dentro del ecosistema de Vercel. Elige S3 si necesitas gran volumen, te importa el coste o compartes el almacenamiento con otros servicios como Lambda o ECS.
¿Puedo usar subidas de archivos en el plan gratuito de Vercel?
Vercel Blob ofrece 500 MB al mes en el plan Hobby (gratuito). Las subidas mediante Serverless Function también funcionan en el plan gratuito, dentro del límite de 4,5 MB, pero cuidado con el tiempo de ejecución de la Function: 10 segundos en Hobby. Para archivos grandes conviene Pro o superior.

Archivos de prueba para este artículo (gratis)