😈 串接 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)」使用 fetchaxios 去呼叫一個**「網域不同」**的 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 重試武器!🚀

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!