Vercel 413 Payload Too Large 解決方案|檔案上傳限制完整處理指南

Vercel 413 Payload Too Large 解決方案|檔案上傳限制完整處理指南

上傳檔案時出現 413 錯誤?

這代表你傳送的資料超過 Vercel 的大小限制了。

不管是 API 請求、檔案上傳,還是部署套件,都有大小限制。

這篇文章教你怎麼處理各種「太大」的問題。


什麼是 413 Payload Too Large?

413 代表:你發送的請求內容超過伺服器允許的大小。

在 Vercel 的情境下,通常是這幾種情況:

情況 常見場景
Request Body 太大 API 上傳檔案超過限制
Serverless Function 太大 部署的 Function 程式碼太大
部署套件太大 整個專案的部署檔案太大

錯誤訊息範例

413 Request Entity Too Large
FUNCTION_PAYLOAD_TOO_LARGE
Error: The Serverless Function exceeds the maximum size limit

看到這些訊息,就知道是大小問題了。


Vercel 的大小限制一覽

了解限制才能避開它們。

Request Body 限制

類型 限制
Serverless Function Request 4.5 MB
Edge Function Request 4.5 MB
靜態 API 路由 無特定限制

注意: 這是請求 body 的大小,包含上傳的檔案。

Serverless Function 限制

項目 Hobby Pro
壓縮後大小 50 MB 250 MB
未壓縮大小 250 MB 500 MB

部署限制

項目 限制
單一檔案大小 100 MB
總部署大小 依方案不同

解決方案一:處理 Request Body 過大

上傳檔案超過 4.5 MB?用這些方法解決。

方法一:壓縮檔案再上傳

// 前端:使用 pako 壓縮
import pako from 'pako';

async function uploadWithCompression(file) {
  const arrayBuffer = await file.arrayBuffer();
  const compressed = pako.gzip(new Uint8Array(arrayBuffer));

  const res = await fetch('/api/upload', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/octet-stream',
      'Content-Encoding': 'gzip',
      'X-Original-Filename': file.name,
    },
    body: compressed,
  });

  return res.json();
}
// 後端:解壓縮
import pako from 'pako';

export async function POST(request) {
  const contentEncoding = request.headers.get('Content-Encoding');
  let data = await request.arrayBuffer();

  if (contentEncoding === 'gzip') {
    data = pako.ungzip(new Uint8Array(data));
  }

  // 處理解壓後的資料
  return Response.json({ success: true });
}

方法二:分割上傳(Chunked Upload)

把大檔案分成小塊上傳:

// 前端:分割上傳
async function uploadInChunks(file, chunkSize = 1024 * 1024) { // 1MB per chunk
  const totalChunks = Math.ceil(file.size / chunkSize);
  const uploadId = crypto.randomUUID();

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);

    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('uploadId', uploadId);
    formData.append('chunkIndex', i.toString());
    formData.append('totalChunks', totalChunks.toString());
    formData.append('filename', file.name);

    const res = await fetch('/api/upload-chunk', {
      method: 'POST',
      body: formData,
    });

    if (!res.ok) {
      throw new Error(`Chunk ${i} upload failed`);
    }

    // 更新進度
    console.log(`Uploaded ${i + 1}/${totalChunks}`);
  }

  // 通知後端合併
  const res = await fetch('/api/upload-complete', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ uploadId, filename: file.name }),
  });

  return res.json();
}
// 後端:接收 chunk
// app/api/upload-chunk/route.ts
import { writeFile, mkdir } from 'fs/promises';
import path from 'path';

export async function POST(request) {
  const formData = await request.formData();
  const chunk = formData.get('chunk');
  const uploadId = formData.get('uploadId');
  const chunkIndex = formData.get('chunkIndex');

  // 暫存到 /tmp(Serverless 環境的暫存空間)
  const tempDir = path.join('/tmp', uploadId);
  await mkdir(tempDir, { recursive: true });

  const chunkPath = path.join(tempDir, `chunk-${chunkIndex}`);
  const buffer = Buffer.from(await chunk.arrayBuffer());
  await writeFile(chunkPath, buffer);

  return Response.json({ success: true });
}

方法三:使用外部儲存服務

最推薦的做法:不要透過 Vercel 的 API 上傳大檔案。

使用 Presigned URL 直接上傳到 S3:

// 後端:產生 presigned URL
// app/api/get-upload-url/route.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({
  region: process.env.AWS_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
});

export async function POST(request) {
  const { filename, contentType } = await request.json();

  const key = `uploads/${Date.now()}-${filename}`;

  const command = new PutObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: key,
    ContentType: contentType,
  });

  const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 3600 });

  return Response.json({ uploadUrl, key });
}
// 前端:直接上傳到 S3
async function uploadToS3(file) {
  // 1. 取得 presigned URL
  const res = await fetch('/api/get-upload-url', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      filename: file.name,
      contentType: file.type,
    }),
  });

  const { uploadUrl, key } = await res.json();

  // 2. 直接上傳到 S3(繞過 Vercel)
  await fetch(uploadUrl, {
    method: 'PUT',
    headers: { 'Content-Type': file.type },
    body: file,
  });

  return key;
}

優點:

  • 沒有大小限制(S3 支援到 5 TB)
  • 不佔用 Vercel 的資源
  • 上傳速度更快

解決方案二:處理 Function 過大

Serverless Function 超過 50 MB?用這些方法瘦身。

檢查 Function 大小

# 使用 Vercel CLI 檢查
vercel build
# 看 output 的大小

或在 Vercel Dashboard → Deployments → 點擊任一 deployment → 看 Functions tab。

常見的肥大原因

1. 引入了不必要的大型套件

// ❌ 引入整個 lodash
import _ from 'lodash';
const result = _.get(obj, 'a.b.c');

// ✅ 只引入需要的函數
import get from 'lodash/get';
const result = get(obj, 'a.b.c');

2. 把 devDependencies 打包進去了

確保 devDependenciesdependencies 分開:

{
  "dependencies": {
    "next": "14.0.0"
  },
  "devDependencies": {
    "typescript": "5.0.0",
    "eslint": "8.0.0"
  }
}

3. 把資料檔案包在 Function 裡

// ❌ 把大型 JSON 放在程式碼中
const bigData = require('./huge-data.json'); // 10 MB

// ✅ 從外部載入
const bigData = await fetch('https://cdn.example.com/data.json').then(r => r.json());

瘦身技巧

1. 使用 dynamic import

// ❌ 靜態引入
import { processImage } from 'sharp';

// ✅ 動態引入,只在需要時載入
export async function POST(request) {
  const { default: sharp } = await import('sharp');
  // 使用 sharp
}

2. 分割 Function

把一個大 Function 分成多個小 Function:

// ❌ 一個大 Function 做所有事
app/api/process/route.ts  // 100 MB

// ✅ 分成多個小 Function
app/api/process/image/route.ts   // 30 MB
app/api/process/video/route.ts   // 40 MB
app/api/process/audio/route.ts   // 30 MB

3. 使用 Edge Functions

Edge Functions 有 4 MB 的限制,但執行速度更快:

// 在 route.ts 中指定使用 Edge
export const runtime = 'edge';

export async function GET() {
  return Response.json({ data: 'ok' });
}

Edge Functions 適合:
- 簡單的 API
- 不需要 Node.js 特有功能
- 需要低延遲

使用 External Packages 設定

next.config.js 中排除大型套件:

// next.config.js
module.exports = {
  experimental: {
    serverComponentsExternalPackages: ['sharp', 'canvas'],
  },
};

這會讓這些套件在執行時從 Node.js 環境載入,而不是打包進 Function。


解決方案三:處理部署過大

整個專案太大,部署失敗?

常見原因

  1. node_modules 包含太多套件
  2. 靜態資源太大(圖片、影片)
  3. Build 輸出太多檔案

解決方法

1. 使用 .vercelignore

# .vercelignore
# 忽略不需要部署的檔案

# 測試檔案
*.test.js
*.spec.js
__tests__

# 開發工具設定
.eslintrc*
.prettierrc*
tsconfig.json

# 文件
docs/
*.md

# 大型資源(應該放 CDN)
videos/
raw-images/

2. 把大型靜態資源放到 CDN

// ❌ 把影片放在專案中
<video src="/videos/demo.mp4" />

// ✅ 放到 CDN
<video src="https://cdn.example.com/videos/demo.mp4" />

推薦的 CDN 服務:
- Cloudflare R2
- AWS CloudFront + S3
- Bunny CDN
- Vercel 的 Image Optimization(僅限圖片)

3. 優化圖片

// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
      },
    ],
  },
};

使用 Next.js 的 Image 組件自動優化:

import Image from 'next/image';

<Image
  src="https://cdn.example.com/image.jpg"
  width={800}
  height={600}
  alt="Description"
/>

4. 清理不必要的依賴

# 找出未使用的依賴
npx depcheck

# 移除不用的套件
npm uninstall unused-package

進階:設定大型檔案上傳系統

完整的大檔案上傳架構範例。

使用 Vercel + Cloudflare R2

// 1. API:產生上傳 URL
// app/api/upload/presign/route.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const r2 = new S3Client({
  region: 'auto',
  endpoint: process.env.R2_ENDPOINT,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY,
    secretAccessKey: process.env.R2_SECRET_KEY,
  },
});

export async function POST(request) {
  const { filename, contentType, size } = await request.json();

  // 檢查大小限制
  const maxSize = 100 * 1024 * 1024; // 100 MB
  if (size > maxSize) {
    return Response.json(
      { error: 'File too large' },
      { status: 413 }
    );
  }

  const key = `uploads/${crypto.randomUUID()}-${filename}`;

  const command = new PutObjectCommand({
    Bucket: process.env.R2_BUCKET,
    Key: key,
    ContentType: contentType,
  });

  const uploadUrl = await getSignedUrl(r2, command, { expiresIn: 3600 });

  return Response.json({ uploadUrl, key });
}
// 2. 前端:上傳元件
'use client';

import { useState } from 'react';

export function FileUploader() {
  const [progress, setProgress] = useState(0);
  const [uploading, setUploading] = useState(false);

  async function handleUpload(file) {
    setUploading(true);

    try {
      // 取得 presigned URL
      const res = await fetch('/api/upload/presign', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          filename: file.name,
          contentType: file.type,
          size: file.size,
        }),
      });

      if (!res.ok) {
        const error = await res.json();
        throw new Error(error.error);
      }

      const { uploadUrl, key } = await res.json();

      // 使用 XMLHttpRequest 追蹤進度
      await new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();

        xhr.upload.onprogress = (e) => {
          if (e.lengthComputable) {
            setProgress(Math.round((e.loaded / e.total) * 100));
          }
        };

        xhr.onload = () => {
          if (xhr.status === 200) resolve();
          else reject(new Error('Upload failed'));
        };

        xhr.onerror = () => reject(new Error('Upload failed'));

        xhr.open('PUT', uploadUrl);
        xhr.setRequestHeader('Content-Type', file.type);
        xhr.send(file);
      });

      return key;
    } finally {
      setUploading(false);
      setProgress(0);
    }
  }

  return (
    <div>
      <input
        type="file"
        onChange={(e) => {
          if (e.target.files?.[0]) {
            handleUpload(e.target.files[0]);
          }
        }}
        disabled={uploading}
      />
      {uploading && <p>上傳中... {progress}%</p>}
    </div>
  );
}

架構說明

用戶                Vercel API              Cloudflare R2
  │                    │                        │
  │ 1. 請求上傳 URL    │                        │
  │───────────────────>│                        │
  │                    │ 2. 產生 presigned URL   │
  │                    │───────────────────────>│
  │                    │<───────────────────────│
  │ 3. 回傳 URL        │                        │
  │<───────────────────│                        │
  │                                             │
  │ 4. 直接上傳檔案(繞過 Vercel)               │
  │────────────────────────────────────────────>│
  │<────────────────────────────────────────────│
  │ 5. 上傳完成                                 │

優點:

  • 大檔案不經過 Vercel,沒有 4.5 MB 限制
  • 上傳速度更快(直接到儲存服務)
  • 降低 Vercel 的資源使用

常見問題 FAQ

Q1:4.5 MB 限制可以提高嗎?

不行。這是 Vercel 的硬性限制,無法透過升級方案提高。

如果需要上傳大檔案,請使用 presigned URL 直接上傳到外部儲存。

Q2:為什麼本機沒問題,部署就 413?

本機開發環境沒有 4.5 MB 的限制。

這是 Vercel Serverless 環境的限制,只有在部署後才會遇到。

Q3:Edge Functions 也是 4.5 MB 限制嗎?

是的。Edge Functions 和 Serverless Functions 的 request body 限制都是 4.5 MB。

Q4:圖片上傳經常失敗,怎麼處理?

  1. 上傳前壓縮圖片
  2. 限制圖片尺寸
  3. 使用 presigned URL 上傳
// 前端壓縮圖片
async function compressImage(file, maxWidth = 1920) {
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => {
      const canvas = document.createElement('canvas');
      const ratio = Math.min(maxWidth / img.width, 1);
      canvas.width = img.width * ratio;
      canvas.height = img.height * ratio;

      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

      canvas.toBlob(resolve, 'image/jpeg', 0.8);
    };
    img.src = URL.createObjectURL(file);
  });
}

Q5:有沒有不需要額外服務的解法?

如果檔案小於 4.5 MB,可以使用壓縮:

  1. 前端壓縮後上傳
  2. 後端解壓縮處理

但如果經常需要上傳大檔案,建議還是使用外部儲存服務。


還是無法解決?

如果你嘗試了以上方法還是有問題:

1. 確認這些資訊

  • 確切的檔案大小
  • 錯誤發生的位置(前端/後端/部署)
  • 使用的方案(Hobby/Pro)

2. 尋求專業協助

大檔案上傳需要正確的架構設計。


Vercel 部署失敗?

Build Error、環境變數、自訂網域,我們幫你快速排除問題。

解決 Vercel 問題


延伸閱讀

分享文章:
V

VibeFix

專門解決 AI Vibe Coding 後的疑難雜症,讓你的專案順利上線。

這篇文章有幫到你嗎?

如果還有問題,讓我們直接幫你解決!

聯繫我們