😈 串接 API 的惡夢:CORS 跨域問題與 Rate Limit
當你開心地跟著前面的教學,在 Postman 裡面把 API 打通了,覺得自己簡直是天才。
結果你興沖沖地把 fetch 寫進網頁裡,打開瀏覽器一跑:
Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
你的畫面一片空白,Console 裡面滿是紅字。恭喜你,你正式碰到了前端工程師的「成人禮」—— CORS (跨來源資源共用)。
本章我們將帶你徹底搞懂 CORS 是什麼,以及如何對付另一個大魔王:Rate Limit (請求頻率限制)。
1. 什麼是 CORS 跨來源資源共用?
CORS (Cross-Origin Resource Sharing) 是一個瀏覽器的安全機制。
請注意這三個字:「瀏覽器」。
Postman 不是瀏覽器、你用 Node.js 寫的後端腳本也不是瀏覽器,所以它們去打 API 絕對不會被 CORS 擋住。只有當你在「網頁前端 (例如 React, Vue, 純 HTML 裡的 JavaScript)」使用 fetch 或 axios 去呼叫一個**「網域不同」**的 API 時,瀏覽器才會啟動這道防護盾。
為什麼要有這個防護盾?
想像一下,如果你登入了銀行網站,然後你不小心點開了一個詐騙網站 (evil.com)。
如果沒有 CORS,這個詐騙網站就可以偷偷在背後用 JavaScript 呼叫銀行的 API:fetch('https://bank.com/api/transfer'),把你戶頭裡的錢轉走!
為了防止這種事,瀏覽器規定:
「當 evil.com 想要去呼叫 bank.com 的 API 時,我 (瀏覽器) 會先去問 bank.com 一句:『欸,你允許 evil.com 存取你嗎?』」
如果 bank.com 沒有回覆 Access-Control-Allow-Origin: * 或是把 evil.com 列入白名單,瀏覽器就會直接把這個請求擋下,並在 Console 噴出那串紅字。
2. 遇到 CORS 該怎麼解?
遇到 CORS,有三種最常見的解法,取決於那支 API 是不是你自己寫的。
解法一:如果是你自己寫的後端 API
最簡單!你只要在後端加上 CORS 的 Header 即可。
以 Next.js API Route 為例:
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
// 你的業務邏輯
const data = { message: "Hello World" };
// 加上 CORS headers 再回傳
return NextResponse.json(data, {
headers: {
'Access-Control-Allow-Origin': '*', // 允許所有人存取 (或填入你指定的網域)
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
解法二:如果是別人的 API (你改不到後端) -> 寫一個 Proxy API!
這是最常見的狀況。例如你想串接政府的公開資料,但政府的伺服器沒設 CORS 白名單,你前端怎麼打就是打不過。
既然「只有瀏覽器會擋 CORS,後端不會擋」,那我們就寫一支自己的後端 API 當作跳板 (Proxy)!
前端去呼叫你自己的 API (因為同網域,沒有 CORS 問題),然後你的 API 去呼叫政府的 API,再把資料傳回前端。
Next.js Proxy API 實戰 (src/app/api/proxy/route.ts):
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
try {
// 1. 從我們自己的後端去打政府的 API (完全沒有 CORS 問題!)
const response = await fetch('https://government.api.com/data');
const data = await response.json();
// 2. 把結果原封不動地傳回給我們自己的前端
return NextResponse.json(data);
} catch (error) {
return NextResponse.json({ error: "抓取資料失敗" }, { status: 500 });
}
}
然後前端只要改成:fetch('/api/proxy'),問題完美解決!
3. 第二大魔王:Rate Limit (頻率限制)
當你成功搞定 CORS,滿心歡喜地寫了一個 useEffect 去抓資料。
結果你不小心把依賴陣列寫錯,導致元件進入無限迴圈,一秒鐘對伺服器發了 1000 個 API 請求。
接著你的畫面又掛了,這次的錯誤訊息是:
HTTP 429 Too Many Requests
這就是 Rate Limit (頻率限制)。
為什麼要有 Rate Limit?
任何伺服器的資源都是有限的。如果有一個惡意駭客寫了個迴圈,一秒鐘打你一萬次 API,你的伺服器很快就會爆炸當機 (這就是所謂的 DDoS 攻擊)。 為了保護伺服器,大部分的商業 API(例如 OpenAI, Stripe, 綠界)都會限制:「一個 IP 位址,一分鐘內最多只能打 60 次 API」。超過這個次數,伺服器就會直接拒絕服務並回傳 429 錯誤。
4. 如何防止被別人的 API 限制?
技巧 1:實作退避重試機制 (Exponential Backoff)
當你呼叫外部 API 收到 429 時,不要馬上重試!你要「等一下」,而且要「越等越久」。
async function fetchWithBackoff(url: string, retries = 3, delay = 1000) {
try {
const response = await fetch(url);
// 如果撞到 Rate Limit
if (response.status === 429 && retries > 0) {
console.log(`被限速了!等待 ${delay} ms 後重試...`);
// 等待指定的毫秒數
await new Promise(resolve => setTimeout(resolve, delay));
// 遞迴重試,且等待時間加倍 (1秒 -> 2秒 -> 4秒)
return fetchWithBackoff(url, retries - 1, delay * 2);
}
return await response.json();
} catch (error) {
throw error;
}
}
// 使用方式
const data = await fetchWithBackoff('https://strict-api.com/data');
技巧 2:在前端實作 Debounce (防抖)
如果你做的是一個「搜尋框」,使用者每打一個字你就去呼叫一次 API,那保證一下就超過頻率限制了。 你必須實作 Debounce,也就是「等使用者停止打字 500 毫秒後,才去打 API」。
// React 中的 Debounce 實戰 (利用 setTimeout)
import { useState, useEffect } from 'react';
export default function SearchBar() {
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
// 設定一個計時器,500ms 後才執行 API
const timer = setTimeout(() => {
if (searchTerm) {
console.log("現在才去打 API: ", searchTerm);
// fetchAPI(searchTerm);
}
}, 500);
// 如果在 500ms 內使用者又打字了,就清掉上一個計時器重新倒數!
return () => clearTimeout(timer);
}, [searchTerm]); // 當 searchTerm 改變時觸發
return (
<input
type="text"
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="輸入關鍵字..."
/>
);
}
搞懂了 CORS 與 Rate Limit,你已經具備了資深串接工程師的思維。以後看到這兩個名詞,你不用再害怕了,因為你擁有最強的 Proxy 跳板與 Backoff 重試武器!🚀