Skip to content

Configuração e limites de upload de arquivo do Vercel | Vercel Blob, limites de API e soluções alternativas

Categoria:Vercel, Configuração de implantação
Este artigo está disponível atualmente apenas em japonês. As versões traduzidas serão publicadas sequencialmente.

Ao implementar upload de arquivo em um app implantado no Vercel, há um limite no tamanho do corpo do Request da Serverless Function. Esse limite de 4.5MB por padrão é frequentemente uma armadilha. Este artigo explora em detalhes o limite de tamanho do corpo da Vercel Function, como configurá-lo em <code>vercel.json</code>, como usar Vercel Blob e como contornar a limitação para lidar com arquivos grandes (upload direto para 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
Figura: Limite de tamanho da Vercel Function e contorno via Blob

Limite de tamanho do corpo da Vercel Function

Vercel Serverless Functions (API Routes, Route Handlers, Edge Functions) têm um limite de tamanho de request. Requests que excedem esse limite resultam em erro 413.

Tipos de Function Limite padrão Valor máximo de configuração Observações
Serverless Function(Node.js) 4.5 MB 4.5 MB Não pode ser alterado em vercel.json
Edge Function 4 MB 4 MB Não pode ser alterado
Next.js Server Actions 1 MB Configurável Alteração em next.config.js

O importante é que o limite de tamanho do corpo da Serverless Function do Vercel é uma <strong>limitação no nível da infraestrutura</strong> e não pode ser contornada alterando configurações no código da aplicação. Para fazer upload de arquivos maiores que 4.5MB, é necessária uma solução arquitetural alternativa.

Configuração em vercel.json

Em <code>vercel.json</code>, você pode configurar o tempo de execução (<code>maxDuration</code>) e memória (<code>memory</code>) da Function, mas não pode alterar o limite de tamanho do corpo. Porém, no caso do Next.js Pages Router, algumas configurações como <code>responseLimit</code> são válidas.

// 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 });
  });
}

Como usar o Vercel Blob

Vercel Blob é um serviço de armazenamento de objetos fornecido pela Vercel. Usando o pacote <code>@vercel/blob</code>, você pode salvar e servir arquivos do seu aplicativo no Vercel sem configuração de 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>
  );
}

Solução alternativa para arquivos grandes: Upload direto para S3

Se você não usar Vercel Blob ou quiser usar S3 diretamente, o método mais confiável é fazer upload direto do cliente para S3 por meio de uma URL Presigned. Como Vercel Function apenas gera tokens, não é afetada pelo limite de tamanho do corpo.

// 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 を経由しないため容量制限なし

Arquivo de teste disponível para usar neste artigo (gratuito)

  • → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Imagem PNG de teste (1MB)</a> — Para verificação de operação de upload para <code>Vercel Blob</code>
  • → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Arquivo de teste PDF (1MB)</a> — Para teste de upload de arquivo dentro do limite de 4.5MB
  • → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">Arquivo de teste ZIP (1MB)</a> — Para teste de upload direto no S3 via Presigned URL

Artigos relacionados

  • → <a href="/ja/blog/nextjs-file-upload/" class="text-primary-600 dark:text-primary-400 hover:underline">Guia de implementação de upload de arquivos no Next.js | App Router, API Route e suporte S3</a>
  • → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">Resumo dos limites de upload de arquivos do AWS S3 e CloudFront</a>
  • → <a href="/ja/blog/netlify-upload-config/" class="text-primary-600 dark:text-primary-400 hover:underline">Como fazer upload de arquivos com Netlify Functions | Limites, Large Media e soluções alternativas</a>