HTTP 快取解析:ETag、Cache-Control 與 CDN
為什麼你的網站感覺很慢(以及快取如何解決這個問題)
你已經優化了圖片、壓縮了 CSS,甚至升級了伺服器。但回訪者仍然體驗到緩慢的載入時間,而你的來源伺服器正在消耗大量頻寬。罪魁禍首是什麼?效率不彰的 HTTP 快取。如果沒有適當的快取標頭,瀏覽器會在每次造訪時重新下載相同的資源,而 CDN 也無法有效發揮作用。
HTTP 快取是現今最具槓桿效應的效能優化手段之一。它能降低延遲、減少頻寬成本,並減輕來源伺服器的負載。在本指南中,我們將拆解關鍵機制:ETag、Cache-Control,以及 CDN 如何融入其中。你將學到正確實作這些機制的實用策略。
HTTP 快取如何運作:整體概觀
當瀏覽器請求一個資源時,它可以從來源伺服器取得,或是提供本機儲存的副本。HTTP 快取定義了儲存的副本何時被視為新鮮,以及何時必須重新驗證的規則。
快取主要分為兩種類型:
- 瀏覽器快取(私有): 使用者的瀏覽器將資源儲存在本機。這對單一使用者在多個頁面或多次造訪之間有好處。
- 共享快取(CDN、代理): 中介伺服器為多個使用者快取資源。這能減少來源伺服器的負載,並加速全球的內容傳遞。
兩者都依賴 HTTP 標頭來決定新鮮度和重新驗證。最重要的兩個標頭是 Cache-Control 和 ETag。
Cache-Control:新鮮度規則
Cache-Control 是定義快取政策的主要標頭。它是一個基於指令的標頭,告訴快取如何處理回應。
關鍵指令
- max-age:回應被視為新鮮的秒數。例如,
Cache-Control: max-age=3600表示回應在 1 小時內是新鮮的。 - s-maxage:類似 max-age,但專門用於共享快取(CDN)。它會覆寫共享快取的 max-age。
- public:回應可以被任何快取儲存,包括共享快取。
- private:回應是針對單一使用者,不應被共享快取儲存。
- no-cache:回應可以被儲存,但每次使用前必須向來源伺服器重新驗證。
- no-store:回應不得儲存在任何快取中。用於敏感資料。
- must-revalidate:一旦過期,快取必須重新驗證才能使用該回應。
- immutable:回應在其新鮮度生命週期內不會改變。適用於帶有版本標記的資源。
範例:Cache-Control: public, max-age=31536000, immutable 是帶有雜湊檔名的靜態資源的理想設定。
ETag 與條件式請求
ETag(實體標籤)是資源特定版本的識別碼。當資源變更時,ETag 也會改變。瀏覽器使用 ETag 發出條件式請求:它們在 If-None-Match 標頭中發送儲存的 ETag。如果資源沒有改變,伺服器會回應 304 Not Modified 且不帶主體,從而節省頻寬。
同樣地,Last-Modified 與 If-Modified-Since 搭配運作,但 ETag 更精確(它們可以偵測同一秒內的變更)。
如何產生 ETag
大多數網頁伺服器和框架會自動產生 ETag。例如,在 Express.js 中,你可以用 app.set('etag', 'strong') 來啟用。在 Nginx 中,靜態檔案預設就會開啟 ETag。
強 ETag(例如 "abc123")保證位元組層級的完全相同。弱 ETag(例如 W/"abc123")表示語意上的等價,而非精確的位元組。
CDN 快取:大規模的共享快取
CDN(內容傳遞網路)扮演全球分散式共享快取的角色。它們在邊緣節點快取你的內容,從鄰近的節點提供給使用者。這能降低延遲並卸載來源伺服器的負擔。
CDN 會遵循 Cache-Control 標頭,但通常也有自己的配置。關鍵概念:
- 邊緣快取: CDN 的本機快取。它根據快取鍵(通常是 URL + 如
Accept-Encoding的標頭)儲存回應。 - 來源屏蔽: 額外的快取層,可減少對來源伺服器的請求。
- 快取失效: CDN 提供 API,讓你在更新資源時清除快取的內容。
使用 CDN 時,設定 Cache-Control 並搭配 s-maxage,以獨立於瀏覽器快取來控制共享快取的新鮮度。例如:Cache-Control: public, max-age=600, s-maxage=3600 表示瀏覽器快取 10 分鐘,但 CDN 快取 1 小時。
快取標頭比較
| 標頭 | 用途 | 範例 |
|---|---|---|
Cache-Control |
定義新鮮度與快取規則 | public, max-age=3600 |
ETag |
資源版本的唯一識別碼 | "abc123" |
Last-Modified |
最後修改的時間戳記 | Wed, 21 Oct 2025 07:28:00 GMT |
Expires |
舊式的絕對到期日期 | Wed, 21 Oct 2025 07:28:00 GMT |
Vary |
指定會影響快取的標頭 | Accept-Encoding |
注意:Expires 已被 Cache-Control 取代,但較舊的用戶端仍會使用。
Web 應用程式的實用快取策略
遵循以下步驟來實作有效的快取:
- 為靜態資源加上指紋: 使用雜湊檔名(例如
app.a1b2c3.js),並設定較長的max-age與immutable。當檔案變更時,雜湊也會改變,從而破壞快取。 - 為 HTML 設定適當的 Cache-Control: HTML 通常應設為
no-cache或較短的max-age,以便使用者快速取得更新。使用ETag進行重新驗證。 - 使用
Vary: Accept-Encoding: 如果你提供壓縮和未壓縮的版本,這能確保快取將它們分開儲存。 - 利用 CDN 與 s-maxage: 為共享快取設定較長的
s-maxage以減少來源負載,同時視需要讓瀏覽器快取保持較短。 - 明智地失效: 部署關鍵更新時,使用 CDN 的清除 API。對於靜態資源,指紋技術可避免需要清除。
- 監控快取命中率: 使用 CDN 分析來確保你的快取有效。低命中率意味著標頭配置錯誤。
常見陷阱與如何避免
- 過度快取 HTML: 使用者會看到過時的內容。使用
no-cache或較短的max-age。 - 快取靜態資源不足: 設定較長的
max-age(例如 1 年)並搭配指紋技術。 - 忽略
Vary: 快取可能提供錯誤的內容(例如 gzip 與純文字)。務必設定Vary: Accept-Encoding。 - 忘記為使用者特定資料設定
private: 共享快取可能洩漏資料。對於已驗證的回應,使用Cache-Control: private。 - 誤用
no-store: 它會阻止所有快取,可能損害效能。僅用於敏感資料。
測試你的快取設定
使用瀏覽器 DevTools(Network 分頁)檢查回應標頭,並查看資源是否從快取提供(尋找「(from disk cache)」或「(from memory cache)」)。若要了解 CDN 行為,請使用 curl -I 檢查如 X-Cache 或 CF-Cache-Status 等標頭。像 WebPageTest 這類工具可以視覺化多次造訪之間的快取情形。
常見問題
ETag 和 Last-Modified 有什麼不同?
ETag 是不透明的識別碼,會在資源變更時改變,而 Last-Modified 是時間戳記。ETag 更精確,因為它們可以偵測同一秒內的變更,且不依賴時鐘同步。
什麼時候應該使用 no-cache 與 no-store?
當你希望快取儲存回應,但每次使用前都向來源伺服器重新驗證時,請使用 no-cache。對於絕不能被任何快取寫入磁碟或記憶體的敏感資料,請使用 no-store。
CDN 如何處理快取失效?
CDN 提供清除 API,讓你可以從邊緣快取中移除特定 URL 或整個目錄。有些也支援軟清除,將內容標記為過時,並在下次請求時重新驗證。為資源加上指紋通常比清除更有效率。
結論
精通 HTTP 快取,搭配 ETag、Cache-Control 和 CDN,是建構快速、可擴展 Web 應用程式的關鍵。從為靜態資源和 HTML 設定適當的標頭開始,利用 CDN 共享快取的 s-maxage,並始終測試你的配置。快取標頭的微小變更就能帶來顯著的效能提升。
需要快速分析伺服器日誌以查看快取命中率嗎?試試我們的 Nginx 日誌分析器 來解析並視覺化你的存取日誌。