Vercel 504 Gateway Timeout 解決方案|2025 超時問題完整排解
部署到 Vercel 後,API 開始報 504 錯誤。
本機明明沒問題,為什麼上線就超時?
這是 Serverless 環境最常見的問題之一。
這篇文章教你如何診斷和解決 Vercel 超時問題。
理解 Vercel 超時限制
執行時間限制
| 方案 | Serverless Functions | Edge Functions |
|---|---|---|
| Hobby(免費) | 10 秒 | 30 秒 |
| Pro | 60 秒 | 30 秒 |
| Enterprise | 900 秒 | 30 秒 |
為什麼會超時?
常見原因:
- 資料庫查詢太慢
- 外部 API 回應慢
- 複雜運算
- 冷啟動 + 慢操作
- 大檔案處理
- 無限迴圈
診斷超時原因
查看 Vercel Logs
- 進入 Vercel Dashboard
- 點擊專案
- 選擇 Deployments
- 點擊 Logs
查看錯誤訊息和執行時間。
加入效能監控
// app/api/slow/route.ts
export async function GET() {
const startTime = Date.now();
// 你的邏輯
const step1 = Date.now();
console.log('Step 1:', step1 - startTime, 'ms');
await someOperation();
const step2 = Date.now();
console.log('Step 2:', step2 - step1, 'ms');
await anotherOperation();
const step3 = Date.now();
console.log('Step 3:', step3 - step2, 'ms');
console.log('Total:', Date.now() - startTime, 'ms');
return Response.json({ success: true });
}
分析瓶頸
常見瓶頸:
- 資料庫連線建立 - 冷啟動時特別明顯
- 複雜查詢 - 沒有索引、大量資料
- 外部 API - 第三方服務回應慢
- 檔案處理 - 大圖片、PDF 生成
解決方案一:優化程式碼
優化資料庫查詢
// ❌ 不好:N+1 查詢
const users = await db.users.findMany();
for (const user of users) {
const orders = await db.orders.findMany({ where: { userId: user.id } });
// ...
}
// ✅ 好:使用 include/join
const users = await db.users.findMany({
include: {
orders: true,
},
});
加入索引
-- 為常查詢的欄位加索引
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_created_at ON orders(created_at);
限制查詢結果
// ❌ 不好:查詢所有資料
const orders = await db.orders.findMany();
// ✅ 好:分頁和限制
const orders = await db.orders.findMany({
take: 20,
skip: (page - 1) * 20,
orderBy: { createdAt: 'desc' },
});
快取查詢結果
import { unstable_cache } from 'next/cache';
const getCachedData = unstable_cache(
async () => {
// 慢查詢
return await db.stats.aggregate();
},
['stats'],
{ revalidate: 300 } // 5 分鐘快取
);
export async function GET() {
const data = await getCachedData();
return Response.json(data);
}
解決方案二:使用串流
什麼是串流?
串流可以在資料準備好時立即發送,而不是等全部完成。
好處:
- 避免超時
- 使用者體驗更好
- 不需要等待
實作串流回應
// app/api/stream/route.ts
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// 發送多個資料塊
for (let i = 0; i < 10; i++) {
const data = await fetchChunk(i);
controller.enqueue(
encoder.encode(JSON.stringify(data) + '\n')
);
}
controller.close();
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
},
});
}
AI 回應串流
// app/api/chat/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamText({
model: openai('gpt-4-turbo'),
messages,
});
// 使用串流回應,避免超時
return result.toDataStreamResponse();
}
解決方案三:使用 Edge Functions
為什麼 Edge 更快?
- 冷啟動幾乎為零
- 全球分散執行
- 更長的超時時間(30 秒)
遷移到 Edge
// app/api/fast/route.ts
export const runtime = 'edge'; // 關鍵
export async function GET(request: Request) {
// 快速操作
return Response.json({ fast: true });
}
Edge 的限制
不支援:
- 完整 Node.js API
- 某些 npm 套件
- 大量記憶體操作
適合:
- 簡單 API 請求
- 認證檢查
- 代理請求
解決方案四:非同步處理
使用佇列
對於長時間任務,改用非同步處理:
// app/api/start-job/route.ts
import { Queue } from '@upstash/queue';
const queue = new Queue();
export async function POST(req: Request) {
const { data } = await req.json();
// 加入佇列,立即返回
const jobId = await queue.push({
type: 'process-data',
data,
});
return Response.json({
jobId,
status: 'processing',
checkUrl: `/api/job-status/${jobId}`,
});
}
// app/api/job-status/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const job = await getJobStatus(params.id);
return Response.json({
id: params.id,
status: job.status,
result: job.result,
});
}
使用 Vercel Cron Jobs
// vercel.json
{
"crons": [
{
"path": "/api/process-queue",
"schedule": "* * * * *"
}
]
}
// app/api/process-queue/route.ts
export async function GET() {
// 每分鐘處理佇列中的任務
const jobs = await queue.pop(10);
for (const job of jobs) {
await processJob(job);
}
return Response.json({ processed: jobs.length });
}
解決方案五:設定更長超時
Vercel Pro 方案
Pro 方案可以延長到 60 秒:
// app/api/slow/route.ts
export const maxDuration = 60; // 60 秒
export async function POST(req: Request) {
// 長時間操作
await longOperation();
return Response.json({ success: true });
}
評估是否需要升級
免費方案夠用的情況:
- 簡單 API
- 快取良好
- 優化過的查詢
需要 Pro 的情況:
- AI 生成
- 大量資料處理
- 複雜報表
解決方案六:分割請求
大任務分成小步驟
// 前端
async function processLargeData(items: any[]) {
const batchSize = 50;
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const result = await fetch('/api/process', {
method: 'POST',
body: JSON.stringify({ batch }),
});
results.push(await result.json());
// 更新進度
updateProgress((i + batchSize) / items.length * 100);
}
return results;
}
使用 Pagination
// app/api/export/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const limit = 100;
const data = await db.records.findMany({
skip: (page - 1) * limit,
take: limit,
});
return Response.json({
data,
hasMore: data.length === limit,
nextPage: page + 1,
});
}
解決方案七:使用外部服務
長時間任務外包
對於真正需要長時間的任務,考慮使用:
| 服務 | 適合場景 |
|---|---|
| AWS Lambda | 15 分鐘以內 |
| Google Cloud Run | 更長時間 |
| Modal | AI/ML 任務 |
| Replicate | AI 模型推論 |
| Trigger.dev | 背景任務 |
整合範例
// app/api/process-video/route.ts
export async function POST(req: Request) {
const { videoUrl } = await req.json();
// 發送到外部服務處理
const job = await fetch('https://api.modal.com/process', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MODAL_API_KEY}`,
},
body: JSON.stringify({ videoUrl }),
});
const { jobId } = await job.json();
return Response.json({
jobId,
status: 'processing',
});
}
預防超時的最佳實踐
1. 設計時考慮超時
// 設定請求超時
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 8000);
try {
const response = await fetch(url, {
signal: controller.signal,
});
clearTimeout(timeout);
return response;
} catch (error) {
clearTimeout(timeout);
if (error.name === 'AbortError') {
// 處理超時
}
throw error;
}
2. 實作快取策略
import { kv } from '@vercel/kv';
export async function GET(request: Request) {
const cacheKey = 'expensive-data';
// 先檢查快取
const cached = await kv.get(cacheKey);
if (cached) {
return Response.json(cached);
}
// 執行慢操作
const data = await expensiveOperation();
// 存入快取
await kv.set(cacheKey, data, { ex: 300 });
return Response.json(data);
}
3. 監控和告警
// 記錄慢請求
export async function GET(request: Request) {
const startTime = Date.now();
try {
const result = await operation();
const duration = Date.now() - startTime;
// 記錄慢請求
if (duration > 5000) {
console.warn('Slow request:', {
path: request.url,
duration,
});
await sendAlert('Slow API detected');
}
return Response.json(result);
} catch (error) {
const duration = Date.now() - startTime;
console.error('Request failed:', { duration, error });
throw error;
}
}
常見問題 FAQ
Q1:10 秒真的不夠用嗎?
對於優化過的程式碼,10 秒通常夠用。
如果不夠:
- 先優化程式碼
- 再考慮升級方案
Q2:Edge Functions 可以完全避免超時嗎?
不行。Edge Functions 有 30 秒限制,而且功能受限。
但對於簡單操作,可以大幅減少超時問題。
Q3:升級 Pro 後就不會超時嗎?
Pro 有 60 秒限制,比免費方案好很多。
但如果程式本身有問題(如無限迴圈),還是會超時。
Q4:如何處理圖片/檔案生成?
- 使用串流
- 使用外部服務(如 Cloudinary)
- 先上傳到 S3,再處理
Q5:資料庫連線慢怎麼辦?
- 使用連線池
- 選擇靠近 Vercel 區域的資料庫
- 使用 Prisma Accelerate 或 PlanetScale
Vercel 超時解決方案重點整理
| 問題類型 | 推薦解決方案 |
|---|---|
| 資料庫慢 | 優化查詢、加索引、快取 |
| 外部 API 慢 | 設定超時、快取、非同步 |
| 複雜運算 | 分批處理、外部服務 |
| 冷啟動 | Edge Functions |
| 需要更長時間 | 升級 Pro、佇列處理 |
Vercel 部署失敗?
Build Error、環境變數、自訂網域,我們幫你快速排除問題。