你第一次打開一個網站時,瀏覽器可能下載 HTML、CSS、JavaScript、圖片和字型。幾秒後再開同一頁,如果所有東西都從伺服器完整下載一次,不只浪費頻寬,也會讓畫面更慢出現。於是 HTTP 有一整套 caching 機制,讓已經取得的 response 在符合條件時可以被重複利用。

HTTP cache 的核心不是「存過就永遠用舊的」,而是讓 client 和 server 對一份 response 能重用多久、何時必須重新確認,建立明確規則。

一句話先記住:cache 解決的是「這份 response 我已經拿過了,現在真的有必要再把整份傳一次嗎?」

Cache 裡存的是 Response

MDN 對 HTTP cache 的描述很直接:cache 會保存和 request 對應的 response,之後遇到可以重用的 request,就可能直接使用先前保存的 response。這表示 cache 不只是圖片暫存資料夾;HTML、CSS、JavaScript、API response 等 HTTP 資源,都可能依照規則被快取。

最靠近使用者的是 browser 的 private cache,只服務那個瀏覽器使用者;網路中也可能存在 shared cache,例如 CDN 或 proxy,讓多個 client 共用已保存的 response。兩者的安全邊界不同,因此同一組 caching 指令不一定適合所有內容。

client → browser cache → shared cache / CDN → origin server

Fresh:還新鮮,就不必問 Server

假設 server 回傳一份 CSS,並告訴 cache 這份 response 在一段時間內仍然有效。只要它還是 fresh,之後相符的 request 就可以直接使用 cache 裡的版本,不必每次都回 origin server 問一次。

Cache-Control: max-age=3600 是常見例子:response 在被產生後的一段時間內可以維持 fresh。這裡的 3600 是秒,也就是一小時。這不是在說檔案一小時後會自動消失,而是超過 freshness lifetime 後,cache 不能再把它當成「不用確認就能直接使用」的新鮮 response。

Stale 不等於立刻把整份重新下載

response 變 stale 後,最笨的做法是直接丟掉,再下載完整內容。但 HTTP 可以做 validation:client 拿著自己已有版本的識別資訊問 server,「我這份還是最新版嗎?」如果沒變,就不需要把內容本體重新傳一次。

這就是 ETag 很常出現的地方。ETag 是特定資源版本的識別值。server 第一次回 response 時可以附上 ETag;之後 client 用 If-None-Match 帶著這個值重新請求。若資源沒有變,server 可以回 304 Not Modified,讓 client 繼續使用本地已有的內容。

第一次:GET → 200 + body + ETag
之後:GET + If-None-Match → 304 → 使用原本 cache

所以 304 並不是「沒有 response」。它是在說:你的 cached representation 還可以用,這次不用再把完整 body 傳回去。

Cache-Control 才是最常見的規則入口

Cache-Control 可以出現在 request 和 response 裡,用 directives 描述 caching 行為。真正做網站時,不需要把所有 directive 一次背完,但有幾個名稱很容易被誤會。

max-age 控制 freshness;public 表示 response 可以被 shared cache 儲存;private 則限制它只能存進 private cache。對包含個人化內容的頁面來說,這個差別尤其重要,因為你通常不希望共享 cache 把某個使用者的內容交給另一個人。

no-cache 不是「不要 Cache」

這是 HTTP caching 最經典的命名陷阱之一。no-cache 並不等於禁止儲存。它要求 cache 在重用 response 前先向 origin server 驗證。也就是說,內容仍然可以被保存,只是不能在沒有 validation 的情況下直接拿來用。

如果你的目的真的是要求 cache 不要儲存 response,通常要看 no-store。這兩個 directive 名字很接近,行為卻不是同一件事。

no-cache:可以存,但使用前要確認。
no-store:不要儲存這份 response。

那為什麼改了 CSS,使用者還看到舊版?

因為 cache 不知道「你心裡認為這是新版」。它只看 URL、response headers、validation 資訊和 caching 規則。如果你讓一個固定 URL 的資源擁有很長的 freshness lifetime,然後直接替換內容,client 可能仍然合理地使用舊 cache。

靜態資源常用的解法是 cache busting:把內容版本反映到檔名或 URL,例如 app.a81f3c.js。內容一改,build system 產生不同 URL;舊檔可以放心 cache 很久,而新版頁面會自然 request 新 URL。這比要求所有使用者「Ctrl+F5」可靠得多。

Cache 和 CDN 是同一件事嗎?

不是。Caching 是一種重用 response 的機制;CDN 則是一套把內容放到更靠近使用者的分散式基礎設施。CDN 很常使用 shared caching,所以兩者經常一起出現,但概念層級不同。

同樣地,應用程式裡的 Redis cache、資料庫 query cache、記憶體 memoization,也都叫 cache,卻不等於 HTTP cache。它們共享「避免重複做昂貴工作」的思想,但保存的位置、key、失效規則與一致性問題完全不同。

Cache 最難的通常不是存,而是失效

把資料存一份副本很容易;難的是知道副本什麼時候不能再相信。cache 太短,效能收益有限;cache 太長,又可能讓使用者長時間看到舊資料。對登入後的個人資料、即時價格、靜態 logo 和帶 hash 的 JavaScript bundle,你不會想使用同一套策略。

因此真正設計 caching 時,應該先問:內容多久會變?舊一點能不能接受?它是不是個人化資料?能不能 validation?URL 變更是否代表內容版本也變?shared cache 能不能看到它?

最後,把 Cache 想成一份有期限的副本

HTTP cache 的價值不是神奇地「讓網站變快」,而是減少不必要的網路傳輸和 origin 工作。fresh response 可以直接重用;stale response 可以先 validation;ETag 和條件式 request 能讓 server 在內容沒變時只回很小的 304,而不必重送整份 body。

所以 cache 真正的問題從來不是「要不要存」,而是「這份副本在什麼條件下還值得相信」。一旦把 freshness 和 validation 分開理解,Cache-Control、ETag、304 這些原本零散的名詞就會連成同一套邏輯。

如果只想先留一句:cache 不是把舊資料藏起來,而是替「什麼時候可以不用重做同一件事」建立規則。

資料來源

HTTP caching、private/shared cache、freshness、validation 與 cache busting 的說明參考 MDN HTTP caching;Cache-Control directives 參考 MDN Cache-Control;ETag 與條件式 request 參考 MDN ETag 與 MDN If-None-Match。