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 打包進去了
確保 devDependencies 和 dependencies 分開:
{
"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。
解決方案三:處理部署過大
整個專案太大,部署失敗?
常見原因
- node_modules 包含太多套件
- 靜態資源太大(圖片、影片)
- 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:圖片上傳經常失敗,怎麼處理?
- 上傳前壓縮圖片
- 限制圖片尺寸
- 使用 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. 確認這些資訊
- 確切的檔案大小
- 錯誤發生的位置(前端/後端/部署)
- 使用的方案(Hobby/Pro)
2. 尋求專業協助
大檔案上傳需要正確的架構設計。
Vercel 部署失敗?
Build Error、環境變數、自訂網域,我們幫你快速排除問題。