HTTP Cache:快取指令與應用
此文章是 FrontendMaster 上的 Advanced Web Development Quiz 課程筆記
問題
將以下快取的指令與其定義配對
[1] no-cache
[2] stale-while-revalidate
[3] no-store
[4] private
[5] must-revalidate
[a] validates a cached response with the origin server before using it, even if it's still fresh
[b] serves stale content while validating the cached response with the origin server
[c] doesn't cache any part of the request or response
[d] prevents caching on shared caches
[e] validates a stale response with the origin server before using it說明
Cache (快取) 就是讓瀏覽器記住之前下載過的東西 (圖片、CSS、JS、甚至 API 資料),下次需要時直接拿出來用,不用再跟伺服器要一次
優點:
- 更快的載入速度: 不用等網路傳輸,直接從本地拿
- 省流量: 使用者的網路流量不會浪費在重複下載相同檔案
- 減輕伺服器負擔: 伺服器不用一直回應相同的請求
對前端來說 Cache 分成兩類
1. 瀏覽器自動管理的 Cache (HTTP Cache)
由瀏覽器根據伺服器回傳的 HTTP header 自動處理,當瀏覽器要取得資源時,會經過以下流程:
- 請求
style.css - 瀏覽器:我的記憶體或硬碟有這個檔案嗎?
- 有,而且還沒過期:直接用,不發請求
- 有,但過期了:發請求問伺服器「這個還能用嗎?」
- 沒有:發請求下載新的
一切都是自動的,前端不需要寫任何的 JavaScript,只需要在伺服器設定 header 即可
2. 前端主動控制的 Cache
localStorage、sessionStorage:用 JS 手動存資料- Service Worker: 可以攔截網路請求,自己決定要回快取還是發請求,用於 PWA 離線功能
- IndexedDB: 可以存大量結構化資料的本地資料庫
HTTP Cache
HTTP Cache 是透過 HTTP Response Headers Cache-Control 來控制的,這些 header 由伺服器端決定並送給瀏覽器,瀏覽器再根據這些資訊決定快取行為
在瀏覽器上的 DevTools 裡面的 Network,點擊任何請求看 Response Headers 可能會看到以下的資訊
Cache-Control: public, max-age=31536000, immutable
Cache-Control: no-cache, must-revalidate
Cache-Control: private, max-age=0, no-store每個指令會被逗號分隔呈現 Cache-Control: <directive>, <directive>, <directive>, ... 的樣子
- 這些指令是組合使用的,不是只能選一個
- 多個指令會一起影響快取行為
- 如果有衝突,通常是「最嚴格」的那個生效
而決定使用快取的流程:
- 是否使用本地快取?
- 若資料還是 fresh > 直接用,不打 server (強快取)
- 若已經過期 (stale),或被要求一定要驗證 > 進入下一階段
- 要不要跟原始伺服器...
- 帶上 ETag/ Last-Modified 等 header
- 若 server 說沒變:回 304 瀏覽器用本地快去 (revalidation)
- 若 server 說有變:回 200,下載新內容
題目所提到的 no-cache、must-revalidate、no-store、private、stale-while-revalidate 都是在控制「這兩個階段要怎麼走」。
補充說明
ETag (Entity Tag)
- 是資源內容的「指紋」或「版本號」
- 是獨立的 header
- 通常是伺服器對檔案內容做 hash,如果內容變了,hash 就變,比
Last-Modified更精準
Last-Modified 記錄資源最後修改的時間
- 記錄資源最後修改的時間
- 是獨立的 header
- 只能精確到秒,如果你在同一秒內改了兩次檔案,可能偵測不到
- 時間可能不準,不同伺服器的時鐘可能有誤差
- 某些情況會失效,檔案內容沒變但時間戳變了(例如重新部署)
no-store
完全不理會 HTTP Cache,每次都直接打伺服器拿一份新的資訊
適合用在金融、醫療、個資這種類型的資料
情境
第一次請求
Response Headers:
HTTP/1.1 200 OK
Cache-Control: no-store第二次請求
因為完全沒有快取可用,所以行為和第一次一模一樣,這次的內容也不會被存到快取。
no-cache
可以快取,但每次使用快取之前都會詢問 server (revalidate)
- 搭配
ETag/Last-Modified,讓流量最小化 - 適合 HTML、重要但不算敏感、常更新的資源
情境
第一次請求
Response Headers:
HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "abc123"
Last-Modified: Wed, 10 Dec 2025 10:00:00 GMT瀏覽器會存快取,但標記成「每次用前要驗證」。
第二次請求
Response Headers:
GET /index.html HTTP/1.1
If-None-Match: "abc123"
If-Modified-Since: Wed, 10 Dec 2025 10:00:00 GMT- 伺服器回應情況 1:沒變
- Network: Status=304, Size=很小(只有 header)
- 瀏覽器拿之前的 body 出來用。
HTTP/1.1 304 Not Modified
ETag: "abc123"- 伺服器回應情況 2:改變
- 下載新 body,更新快取。
HTTP/1.1 200 OK
ETag: "def456"must-revalidate
- 在 fresh 的狀態下可以直接用快取 (強快取),一但過期 (stale) 就必須跟 server 驗證 (revalidate)
- 不能接受「過期資料」的資訊(價格、庫存),但在 fresh 時可以使用快取
情境
第一次請求
Response Headers:
HTTP/1.1 200 OK
Cache-Control: max-age=60, must-revalidate
ETag: "v1"第二次請求 (在 60 秒內)
和「強快取命中」相同:
- 直接從快取讀,不發 request。
- Network 顯示 200 (from disk/memory cache)。
第三次請求 (超過 60 秒)
一定會發「協商請求」,就像 no-cache 那樣帶上 If-None-Match/If-Modified-Since。
回應不是 200 就是 304。
stale-while-revalidate
過期後的一段時間內,可以先用舊的回應使用者,同時在背景去更新快取。
- 時間在 0 ~ max-age 間:直接使用快取 (fresh)
- max-age ~ max-age + stale-while-revalidate: 可以用快取,但要背景 revalidate
- 超過以上範圍: 不能再用快取,必須向
must-revalidate先去驗證
適合對時效性沒這麼敏感的內容:部落格、文章列表
情境
第一次請求
Response Headers:
HTTP/1.1 200 OK
Cache-Control: max-age=60, stale-while-revalidate=300
ETag: "v1"第二次請求 (0~60 秒)
和「強快取命中」完全一樣:直接快取,不打網路
第三次請求 (60~360 秒)
前端的感受:
- 使用者 → 立刻拿到舊的快取(幾乎像強快取)
- 背景 → 瀏覽器同時對伺服器發一個「協商請求」(帶 If-None-Match 等)
如果伺服器回 304 → 更新 freshness,下次就還是當 fresh 用
如果伺服器回 200 → 把新內容寫進快取,但這次畫面還是舊的,下一次才會用到新內容
private
這主要影響瀏覽器以外的中間層快取 (CDN、proxy)
public: 任何快取都可以存(瀏覽器、CDN、proxy),CDN 等共享快取也可以把「第一次回應」存下來,幫很多使用者當「第二次」用private: 只有使用者端的私有快取 (瀏覽器) 可以存
適合個人化資料 (個人儀表板、帳戶頁),可以讓瀏覽器快取,但不想讓 CDN 共享給別人
Response Headers:
Cache-Control: private, max-age=0, no-cache同一個資源,世界各地的使用者是各自有自己的第一次/第二次,還是大家共用 CDN 的快取
