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
Este artículo está disponible actualmente solo en japonés. Estamos trabajando en las traducciones.

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).

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 <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>

🛠️ Herramientas relacionadas de DevLab