GCP帳號充值 解決GCP CDN導致跨域請求失敗
第一章 事故現場:為什麼「明明都開了」還是失敗
跨域請求失敗通常不會發生在「你真的沒開」的情況下。更常見的是:你以為已經設定了 CORS,但瀏覽器仍然把請求攔下來。當你把 GCP CDN(Cloud CDN)放在前面,這件事會變得更微妙:CDN 是快取與回源的中間層,它可能在你看不到的地方把回應「改了」、或是把你以為每次都會產生的標頭變成了偶發。
你可能遇到以下表象:
- GCP帳號充值 瀏覽器控制台顯示:No 'Access-Control-Allow-Origin' header
- 或提示:CORS policy: Response to preflight request doesn't pass access control check
- 又或網路請求看到 HTTP 4xx/5xx,但你其實只是在前端層看見「被 CORS 拒絕」,真正原因藏在回源/快取行為
本文以標題「解決 GCP CDN 導致跨域請求失敗」為主線,從最常見的幾個錯誤開始拆解,並給出能直接照做的修正清單。你不需要先熟悉所有 GCP 細節,但需要掌握一件事:瀏覽器跨域失敗的根因幾乎一定在「回應的 HTTP 標頭」或「預檢流程」,而 CDN 會直接影響這兩者。
GCP帳號充值 第二章 先確認:你到底是哪一種跨域失敗
在修配置之前,先把現象分類。這一步會大幅節省時間,避免你在錯誤的方向上反覆調整。
2.1 沒有 CORS 標頭:最常見的第一類
當你在控制台看到類似「沒有 Access-Control-Allow-Origin」的錯誤,多半表示回應中沒有帶 CORS 標頭。注意:很多後端只對特定路徑或特定方法回傳標頭,CDN 可能會把你本來會走到「正確處理邏輯」的請求快取成另一種回應,導致偶發缺標頭。
2.2 預檢 OPTIONS 失敗:第二類
當請求是 PUT/DELETE、或帶自訂 Header、或 Content-Type 不是簡單範疇,瀏覽器會先送 OPTIONS 預檢。此時 CDN 若沒有正確把 OPTIONS 轉發到後端(或後端對 OPTIONS 沒有回應 CORS),就會在「預檢」階段直接失敗。
你可以回到瀏覽器 DevTools → Network,特別看 OPTIONS 的回應狀態碼,以及是否包含:
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- Access-Control-Max-Age(可選)
2.3 緩存導致標頭被覆蓋:第三類
Cloud CDN 最容易讓人誤判。因為你可能在測試時覺得「後端確實有 CORS 標頭」,但第一次請求命中快取後,之後的回應就不再經過你的後端邏輯。若你沒有正確控制快取鍵、快取行為、或回應標頭保留策略,就可能出現「有時候可以、有時候不行」的怪現象。
第三章 理論地圖:CDN 在跨域流程中的角色
理解 CDN 的位置很關鍵。你的請求流程大概如下:
- 瀏覽器送出跨域請求到 CDN 域名
- CDN 判斷是否快取命中
- 若命中:直接回傳快取內容(標頭也可能是快取版本)
- GCP帳號充值 若未命中:請求回源到你的後端(或 Cloud Run/負載平衡器/代理)
- 後端生成回應並附上 CORS 標頭
- CDN 可能會根據配置決定是否把標頭納入快取、或是否在回傳給前端前「整理/改寫」某些內容
因此,要修復問題,你要同時考慮「後端有沒有」以及「CDN 有沒有讓它被正確回傳」。很多人只檢查後端,卻忽略了 CDN 的快取與選路行為。
第四章 常見成因一:CORS 標頭只在某些方法回傳
許多後端框架對於 GET/POST 會自動加上 CORS,而對於 OPTIONS 或其他方法則不一定。當瀏覽器發送預檢 OPTIONS 時,如果你沒有明確處理,後端可能回傳沒有 CORS 的 204 或 404。
修正策略很直接:確保 OPTIONS 也回應正確的 CORS 標頭。
4.1 最小可用的 CORS 回應(概念層級)
對跨域資源常見會需要以下幾個標頭(示意概念,不同框架具體寫法不同):
- Access-Control-Allow-Origin:可設為單一來源域名或通配(但帶憑證時不可用通配)
- Access-Control-Allow-Methods:例如 GET, POST, PUT, DELETE, OPTIONS
- Access-Control-Allow-Headers:例如 Content-Type, Authorization,自訂 header 也要包含
- Access-Control-Max-Age:可選,用於快取預檢結果
重點:預檢成功後,瀏覽器才會發送實際請求。若 OPTIONS 沒過,你的主流程再怎麼漂亮都沒用。
4.2 在 GCP 上,別忽略負載平衡器層的處理差異
如果你的架構是 Cloud Load Balancing + Cloud CDN,OPTIONS 的流量可能會先碰到特定轉發規則、或被某層判斷為不該轉發。你需要確認 OPTIONS 的處理路徑完整通過,並在回應中帶上 CORS 標頭。
第五章 常見成因二:CDN 快取把錯誤回應「存起來」
這是 GCP CDN 導致跨域失敗最讓人抓不到的原因之一。當第一個請求(可能沒有帶正確標頭、或是 OPTIONS 先來)返回了不含 CORS 的回應,CDN 可能把它快取下來。之後的請求即使後端已修好,仍可能因為快取命中而繼續失敗。
5.1 你看到的是後端狀態正確,但瀏覽器仍失敗
你可能在後端日誌看到 OPTIONS 跑過了,也看到了 CORS 標頭。但瀏覽器依舊失敗。原因往往是:瀏覽器並沒有拿到你後端新產生的回應,而是拿到 CDN 仍在快取中的舊版本。
5.2 快取行為與標頭:你需要知道 CDN 是如何「保存」回應
不同 CDN 產品對快取鍵、以及哪些 header 參與快取會有差異。但在修復上,你要牢記兩個原則:
- 不要讓缺少 CORS 的回應被快取(至少對需要 CORS 的 API/路徑如此)
- 讓 CDN 對預檢 OPTIONS 不做不必要快取
實務上,你要做的是:檢查你對 API 路徑的快取政策(例如 clientTtl/maxTtl、是否尊重 Cache-Control、是否依賴特定 header)。若你不確定,最簡單的驗證方式是:暫時停用快取或對特定 path 設置更保守策略,再觀察問題是否消失。
第六章 常見成因三:Cache-Control 與標頭策略不匹配
如果後端回應中沒有清楚的 Cache-Control 指令,CDN 可能會用自己的預設策略快取內容。你以為的是「每次都走回源」,但 CDN 可能默默做了相反的事。
尤其對於 API,通常不建議讓 CDN 長時間快取「會因請求頭而不同」的內容(例如 Authorization 相關)。雖然 CORS 本質是瀏覽器安全策略,但在實作上回應 header 與內容可能會被請求上下文影響;你一旦讓 CDN 快取,CORS 就可能不穩定。
6.1 對跨域 API 的建議:先把一致性打穩
修復時可以採取保守策略:
- 對需要 CORS 的 API 路徑,先設低 TTL 或直接不快取(或至少不快取 OPTIONS)
- 確保所有會被 CDN 可能快取到的回應都包含 CORS 標頭(包含錯誤碼回應)
- 對是否需要憑證(credentials)做明確設計,避免通配與 credential 混用
GCP帳號充值 第七章 常見成因四:Access-Control-Allow-Origin 設定不當
很多團隊會用「允許所有來源」的方式快速通過測試,但一旦前端帶上憑證(fetch 的 credentials 設成 include,或使用 cookie/token 相關),就會踩雷。
瀏覽器規則很嚴格:
- 當你回應 Access-Control-Allow-Origin: * 時,通常不能搭配 Access-Control-Allow-Credentials: true
- 若需要憑證,你必須回傳具體的 origin(例如 https://example.com),而不是通配
在 GCP CDN 場景下,這個問題會被放大:如果你用通配可以暫時通過 GET,但預檢或其他方法因為走到不同回應分支,可能就突然失敗。
7.1 用「單一已知來源」策略更穩
如果你確定只有一兩個前端域名,建議直接回傳具體來源。這樣不但符合瀏覽器規則,也讓快取行為更可控:至少不需要把 origin 視為變動維度。
GCP帳號充值 第八章 實戰修正清單:一步步把問題解掉
下面是一套可落地的排查與修正流程。你不需要一次做完,但建議照順序來,因為每一步都會縮小範圍。
8.1 第一步:用 Network 抓證據,確認是 OPTIONS 還是實際請求
- 打開 DevTools → Network
- 篩選你的 API 請求
- 確認失敗的是 OPTIONS(預檢)還是 GET/POST/PUT/DELETE
- 記錄失敗請求的回應狀態碼與 response headers
如果 options 都沒出現或回應沒有 CORS 標頭,那你的優先級就是先處理預檢流程,而不是調整快取。
8.2 第二步:直接在後端驗證「所有方法/所有分支」都回 CORS
你要確保:
- GET/POST 等主流程都有 CORS
- OPTIONS 也回 CORS
- 即使是 4xx/5xx,也回 CORS(否則瀏覽器依舊會拒絕讀取回應)
很多人只在成功分支加了 middleware,錯誤分支沒有。CDN 對錯誤也可能快取,導致更難排。
8.3 第三步:暫時降低 CDN 快取,觀察問題是否消失
在不確定快取是否造成干擾時,最快的驗證就是「讓它不要快取」。你可以:
- 對 API 路徑臨時設置更保守的快取 TTL
- 或暫時停用 Cloud CDN(至少針對測試階段)
- 或對快取清除/刷新(invalidation)後重測
若快取關掉後跨域立刻正常,基本可以判定你的問題落在:CORS 標頭未被正確納入回傳或被快取覆蓋。
8.4 第四步:讓 CDN 對 OPTIONS 不做不當處理
預檢 OPTIONS 往往不需要被快取。你需要確認:
- CDN 不會把 OPTIONS 的回應快取成錯誤版本
- OPTIONS 會正確轉發到回源(或由邊緣直接生成合理回應)
若你的架構允許,在邊緣或負載平衡層直接生成 OPTIONS 的回應也可以,但前提是標頭一致且正確。
8.5 第五步:重新調整快取鍵與快取策略(避免 origin 變動造成錯配)
若你的後端會根據 Origin 回傳不同的 Access-Control-Allow-Origin,那麼你要確保 CDN 不會把某個 origin 的回應拿來回其他 origin。
GCP帳號充值 你可以採取兩種策略:
- 讓後端回應固定的 Access-Control-Allow-Origin(例如只允許單一來源)
- 或調整快取行為,使其不會把 origin 差異混在同一份快取內容(這通常需要更進一步的配置理解)
8.6 第六步:清掉快取、再做全量測試
修完配置後請務必做兩件事:
- 清除/失效(invalidation)可能受影響的快取
- 從新開視窗或使用無痕模式測試,避免被瀏覽器預檢快取(Access-Control-Max-Age)干擾
GCP帳號充值 第九章 常見陷阱:你可能已經踩過但沒注意
9.1 協定或端口造成的 origin 不一致
origin 不只是網域。http/https、以及 port 不同都可能導致瀏覽器判定為不同 origin。你可能在後端回傳了正確網域,但忘了協定是 https 還是 http,或前端其實在另一個子域/不同端口上。
9.2 多層代理導致標頭在途中被移除
有些架構會經過多個中間層(例如 API Gateway、負載平衡器、應用服務)。如果其中一層對某些標頭不透明,CORS 標頭可能在中間被移除或覆寫。這時你必須在「最終返回瀏覽器前」的位置觀察 response headers,而不是只看後端輸出。
9.3 預檢成功但主請求仍失敗
預檢只驗證你「允許哪些方法與標頭」。如果主請求帶了別的 header,或 Content-Type 不在允許列表,主請求就會被拒絕。你要檢查 Access-Control-Allow-Headers 是否包含實際使用的 header。
第十章 一個可重複的驗證流程:讓你下次不再靠運氣
很多團隊修 CORS 是靠「反覆改、反覆試」;而一旦引入 CDN,這種方式會變得昂貴。你可以用下面流程建立可重複的驗證:
- 固定測試環境:用無痕或新分頁,避免瀏覽器快取影響
- 先看 OPTIONS:確定預檢回應包含正確 CORS 標頭
- 再看主請求:確認實際回應也帶 CORS
- 確認是否命中 CDN 快取:若你的工具能看到 cache status(例如 HIT/MISS),就用它判斷
- 在修復後做快取失效:避免用舊回應誤判
只要你每次都能定位到「缺哪個標頭」、「缺在哪個階段」、「是否命中快取」,問題就會越來越快解。
第十一章 給你的最終建議:把 CORS 與 CDN 當成同一個系統設計
GCP CDN 導致跨域請求失敗的本質不是 CDN 壞了,而是跨域需要的 HTTP 行為必須在邊緣與回源之間一致。如果你的 CORS 實作只考慮後端,忽略了 CDN 對回應的快取、保留與重用,就會出現看似矛盾的結果:今天能、明天不行;有些路徑行,有些路徑不行;錯誤碼也會導致跨域失敗。
落地到工程層面,你可以遵循三個原則:
- 預檢(OPTIONS)是第一優先級:沒有它,其他都白搭
- 所有可能回應都要一致帶 CORS:包含錯誤分支與邊緣回應
- 控制 CDN 快取與快取鍵的影響範圍:至少先確保修復後不會被舊快取覆蓋
結語:把失敗原因釘在證據上
跨域失敗最可怕的地方在於它常常只給你一句「被 CORS 攔下」。但只要你能在 Network 裡看見回應標頭、回應狀態碼、以及 OPTIONS/主請求的差異,你就能把問題從「猜」變成「定位」。當你再把 CDN 的快取行為納入考量,這類問題就會變成可控、可修、可驗證的工程任務。
如果你願意,把你的架構(用的是哪種 GCP 入口:Cloud Load Balancing 還是 API Gateway?後端是 Cloud Run 還是其他服務?)以及瀏覽器 Network 裡失敗那筆請求的 response headers(遮掉敏感資訊)貼出來,我可以幫你把修正步驟更精準地對應到你的場景。

