コンテンツにスキップ

Vercelのファイルアップロード設定と上限|Vercel Blob・API制限・回避策

カテゴリ:Vercel・デプロイ設定

Vercel にデプロイしたアプリでファイルアップロードを実装する際、Serverless Function のリクエストボディには上限があります。デフォルトで 4.5MB というこの制限はしばしばはまりポイントになります。本記事では Vercel Function のボディサイズ制限、vercel.json での設定方法、Vercel Blob の使い方、そして制限を回避して大容量ファイルを扱う方法(S3 への直接アップロード)まで詳しく解説します。

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 を直接使いたい場合は、Presigned 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 を経由しないため容量制限なし

この記事で使えるテストファイル(無料)

📚 関連記事

PNG vs WebP vs AVIF|画像フォーマットの選び方と変換方法

PNG / JPEG / WebP / AVIF の特徴・用途・ブラウザ対応状況を比較。picture 要素での出し分け、DevLab の画像フォーマット変換ツールの使い方も解説。

2026-04-18

Whois でドメイン情報を調べる方法|有効期限・ネームサーバー・登録者

Whois でわかること (登録者・有効期限・レジストラ・NS)、GDPR によるプライバシー保護の影響、ドメイン管理の実務的な使い方を解説。

2026-04-18

HTTP ステータスコード完全ガイド|よくあるエラーの原因と対処法

開発者が頻出する HTTP ステータスコード (200/301/302/400/401/403/404/413/422/429/500/502/503/504) の意味・原因・対処法を解説。301 vs 302 の SEO 影響、400 vs 422 の使い分けも。

2026-04-18

cURL コマンドを JavaScript fetch・Python requests に変換する方法|DevTools 連携

Chrome DevTools の Copy as cURL を fetch / axios / Python requests / PHP cURL / Go net/http に変換する手順を解説。主要 cURL オプション (-X / -H / -d / -F / -u / -b / -L) の変換パターン、認証トークンの扱い、注意点まで。

2026-04-16

Cookie のセキュリティフラグ完全ガイド|Secure / HttpOnly / SameSite / __Host-

Cookie のセキュリティ属性 Secure / HttpOnly / SameSite (Strict/Lax/None) / __Host- __Secure- プレフィックス / 4096 バイト制限を解説。CSRF / XSS / セッションハイジャック対策と、Laravel / Express の実装例。

2026-04-16

JWT のセキュリティベストプラクティス|alg none 攻撃 / 有効期限 / 署名検証

JWT (JSON Web Token) の代表的な脆弱性 6 種類 (alg none 攻撃 / 鍵混同 / 無期限トークン / payload への機密情報 / 失効不可 / 弱いシークレット) と対策。リフレッシュトークンパターン、失効リスト、HttpOnly Cookie 格納まで。

2026-04-16