Vercel 429 Too Many Requests 解決方案|API 限流處理完整指南
API 一直回傳 429 錯誤?
這代表你發送的請求太多、太快了。
Vercel 為了保護系統穩定,會對請求頻率進行限制。
這篇文章教你怎麼處理 429 錯誤,並避免再次發生。
什麼是 429 錯誤?
429 Too Many Requests 代表:你在短時間內發送了太多請求。
這是一種「限流」機制,英文叫 Rate Limiting。
為什麼需要限流?
- 保護伺服器:避免被大量請求壓垮
- 公平使用:確保每個用戶都能正常使用
- 防止濫用:阻止惡意攻擊或爬蟲
什麼情況會觸發 429?
| 情況 | 範例 |
|---|---|
| 短時間大量請求 | 1 秒內發送 100 個 API 請求 |
| 超過方案限制 | 超過免費方案的請求額度 |
| 循環呼叫 | 程式碼寫錯導致無限循環 |
| 爬蟲攻擊 | 被惡意爬蟲大量訪問 |
Vercel 的 Rate Limit 規則
Vercel 有多種限制,了解它們才能避開。
部署相關限制
| 項目 | Hobby | Pro |
|---|---|---|
| 每天部署次數 | 100 | 6,000 |
| 並行 Build | 1 | 12 |
| Build 佇列 | 10 | 100 |
API 相關限制
| 項目 | 限制 |
|---|---|
| Serverless Function 並行執行 | 依方案不同 |
| Edge Function 請求 | 每秒數千次(依區域) |
| API 路由 | 無硬性限制,但有 fair use |
Vercel API(管理用 API)
| 項目 | 限制 |
|---|---|
| API 請求 | 每分鐘 500 次 |
| 認證請求 | 每分鐘 100 次 |
注意: 這些是 Vercel 管理 API(用於部署、設定等),不是你自己的 API。
解決方案一:優化請求模式
最根本的解法:減少不必要的請求。
合併請求
// ❌ 不好:每個項目單獨請求
for (const id of ids) {
const item = await fetch(`/api/items/${id}`);
items.push(item);
}
// ✅ 好:一次請求多個項目
const items = await fetch('/api/items', {
method: 'POST',
body: JSON.stringify({ ids }),
});
使用快取
// ✅ 前端快取
const cache = new Map();
async function fetchWithCache(url) {
if (cache.has(url)) {
return cache.get(url);
}
const data = await fetch(url).then(r => r.json());
cache.set(url, data);
return data;
}
減少輪詢頻率
// ❌ 不好:每秒輪詢
setInterval(() => fetch('/api/status'), 1000);
// ✅ 好:合理的輪詢間隔
setInterval(() => fetch('/api/status'), 10000);
// ✅ 更好:使用 WebSocket 或 SSE
const eventSource = new EventSource('/api/status-stream');
eventSource.onmessage = (e) => updateStatus(JSON.parse(e.data));
防抖和節流
// ✅ 搜尋框使用 debounce
import { debounce } from 'lodash';
const debouncedSearch = debounce(async (query) => {
const results = await fetch(`/api/search?q=${query}`);
setResults(results);
}, 300);
// 用戶每次輸入都會呼叫,但實際請求只在停止輸入 300ms 後發送
解決方案二:實作 Retry 機制
遇到 429 時,等一下再試。
基本 Retry
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
const res = await fetch(url);
if (res.ok) {
return res.json();
}
if (res.status === 429) {
// 等待後重試
const waitTime = Math.pow(2, i) * 1000; // 1s, 2s, 4s
console.log(`Rate limited, waiting ${waitTime}ms...`);
await new Promise(r => setTimeout(r, waitTime));
continue;
}
throw new Error(`HTTP ${res.status}`);
}
throw new Error('Max retries exceeded');
}
指數退避(Exponential Backoff)
async function fetchWithExponentialBackoff(url, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const res = await fetch(url);
if (res.ok) {
return res.json();
}
if (res.status === 429) {
// 從 Retry-After header 取得建議等待時間
const retryAfter = res.headers.get('Retry-After');
const waitTime = retryAfter
? parseInt(retryAfter) * 1000
: Math.min(1000 * Math.pow(2, attempt), 30000); // 最多等 30 秒
console.log(`Rate limited, waiting ${waitTime}ms...`);
await new Promise(r => setTimeout(r, waitTime));
continue;
}
throw new Error(`HTTP ${res.status}`);
} catch (error) {
if (attempt === maxRetries - 1) throw error;
}
}
}
使用現成的 Library
// 使用 axios-retry
import axios from 'axios';
import axiosRetry from 'axios-retry';
axiosRetry(axios, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (error) => {
return error.response?.status === 429;
},
});
解決方案三:自訂 Rate Limiting
在你自己的 API 加入限流,保護後端。
使用 Upstash Rate Limit
// 使用 Upstash 的 Rate Limit 套件
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'), // 每 10 秒最多 10 次
});
export async function GET(request) {
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous';
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
return Response.json(
{ error: 'Too many requests' },
{
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': reset.toString(),
},
}
);
}
// 正常處理請求
return Response.json({ data: 'ok' });
}
使用記憶體限流(簡單版)
// 注意:這個方法在 Serverless 環境效果有限
const requestCounts = new Map();
function rateLimit(ip, maxRequests = 10, windowMs = 60000) {
const now = Date.now();
const windowStart = now - windowMs;
if (!requestCounts.has(ip)) {
requestCounts.set(ip, []);
}
const requests = requestCounts.get(ip).filter(time => time > windowStart);
requests.push(now);
requestCounts.set(ip, requests);
return requests.length <= maxRequests;
}
export async function GET(request) {
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous';
if (!rateLimit(ip)) {
return Response.json({ error: 'Too many requests' }, { status: 429 });
}
return Response.json({ data: 'ok' });
}
解決方案四:升級方案
如果合理使用還是超過限制,可能需要升級。
Hobby vs Pro 比較
| 項目 | Hobby | Pro |
|---|---|---|
| 每日部署 | 100 | 6,000 |
| Function 執行 | 100 GB-Hours | 1000 GB-Hours |
| 頻寬 | 100 GB | 1 TB |
| 價格 | $0 | $20/月 |
什麼時候該升級?
- 每天部署超過 100 次
- Function 執行時間經常超額
- 流量超過免費額度
- 需要更多並行 Build
不確定該不該升級?可以聯繫我們幫你評估。
防止 429 的最佳實踐
預防勝於治療。
1. 監控請求量
// 在 API 中記錄請求量
export async function GET(request) {
console.log('[API] Request received:', new Date().toISOString());
// 你的邏輯
}
2. 使用佇列處理大量任務
// 不要一次發送大量請求
// ❌
await Promise.all(users.map(u => sendEmail(u)));
// ✅ 使用佇列,控制並行數量
import pLimit from 'p-limit';
const limit = pLimit(5); // 最多 5 個並行
await Promise.all(users.map(u => limit(() => sendEmail(u))));
3. 快取 API 回應
// 在 Next.js 中使用快取
export async function GET() {
return Response.json(data, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
},
});
}
4. 使用 CDN
把靜態資源和可快取的 API 放到 CDN,減少對 Vercel 的請求。
常見問題 FAQ
Q1:429 錯誤會持續多久?
通常幾秒到幾分鐘。
Vercel 會在 Retry-After header 中告訴你應該等多久。
Q2:怎麼知道我離限制還有多遠?
查看 Vercel Dashboard → Usage。
可以看到當前的使用量。
Q3:被限流會影響 SEO 嗎?
可能會。如果 Google 爬蟲遇到 429,可能會減慢爬取速度。
但偶爾的 429 不會造成大問題。
Q4:如何區分是 Vercel 的限制還是我自己的 API 限制?
- Vercel 的限制:通常發生在部署、Function 執行等
- 你的 API 限制:需要自己實作
查看錯誤發生的位置來判斷。
還是無法解決?
如果你的 429 問題持續發生:
1. 確認以下資訊
- 錯誤發生的位置(前端/後端/部署)
- 請求頻率
- 使用的方案(Hobby/Pro)
2. 尋求協助
有些限流問題需要架構調整才能解決。
Vercel 部署失敗?
Build Error、環境變數、自訂網域,我們幫你快速排除問題。