跳到内容

Vercel 文件上传配置和限制 | Vercel Blob、API 限制和解决方案

分类:Vercel·部署配置

在 Vercel 上部署的应用中实现文件上传时,Serverless Function 的请求体大小有限制。这个默认的 4.5MB 限制经常成为痛点。本文详细解释了 Vercel Function 的请求体大小限制、vercel.json 中的配置方法、如何使用 Vercel Blob,以及如何通过绕过限制来处理大文件(直接上传到 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
图:Vercel Function 大小限制与通过 Blob 绕过

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主体大小限制是基础设施级别的限制,无法通过更改应用程序代码设置来绕过。上传超过4.5MB的文件需要架构级的解决方案。

vercel.json 中的配置

vercel.json 中,您可以配置 Function 执行时间(maxDuration)和内存(memory),但无法更改正文大小限制。不过,对于 Next.js Pages Router,某些配置(如 responseLimit)是有效的。

// 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 提供的对象存储服务。使用 @vercel/blob 包,无需配置 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 を経由しないため容量制限なし

❓ 常见问题

有办法提高 Vercel 的上传上限吗?
Vercel Serverless Function 的 4.5MB 请求体上限属于基础设施层面的限制,无法通过 vercel.json 或应用代码修改。超过 4.5MB 时,请使用 Vercel Blob 的客户端上传,或通过 S3 预签名 URL 由客户端直传 S3。两种方式文件都不经过 Function,因此不受该限制影响。
该用 Vercel Blob 还是 AWS S3?
Vercel Blob 与 Vercel 集成简单、几乎无需配置,但存储成本高于 S3,适合中小型项目或希望留在 Vercel 生态内的团队。若需要大容量存储、注重成本,或需与 Lambda、ECS 等其他服务共用,则更适合 S3。
Vercel 免费套餐能使用文件上传吗?
Vercel Blob 在 Hobby(免费)套餐下每月有 500MB 免费额度。经由 Serverless Function 的上传在 4.5MB 上限内免费套餐同样可用,但要注意 Function 的执行时间限制(Hobby 为 10 秒)。处理大文件建议使用 Pro 及以上套餐。

本文中可用的测试文件(免费)