Cache control
Cache control
當有使用者造訪網站時,他們的瀏覽器會將某些資源(例如影像和網站資料)儲存在稱為快取的存放區中。
若重新造訪同一網站時,快取控制會設定規則,以確定該使用者是將從其本機快取中載入這些資源,還是瀏覽器必須向伺服器傳送請求以獲取新資源。
主要就是針對 Cache 來控制瀏覽器如何與快取的內容互動
有分成以下幾種
Not Cacheable
- Cache-Control: no-cache
- Cache-Control: no-store
no-cache:
- 會快取,但每次請求前都先向伺服器用 ETag 檢查是否有更新檔案,確認這些資源自上次緩存以來是否有更改。
如果資源未更新,則伺服器回傳 304 (Not Modified),則 可以從本地快取中提供,而不是重新下載 如果資源已更新不一樣,就會重新取得新資源 - 等同於
max-age=0, must-revalidate - 強調的是在重用快取之前的驗證
no-store:
- 不要讓瀏覽器快取
- 表示禁止儲存快取,每次都必須跟伺服器要求新檔案,適用於私人或機密資料。
Immutable
Immutable
這些資源可以被瀏覽器無限期地存儲,因為它們永遠不會改變。
Cache-Control: public, max-age=31536000, immutable
有些檔案的名稱中會加上一串 SHA-hash (app.34dgew22.js)。這個檔案內容一定不會有變更,因為當檔案變更的時候,就會產生新的 hash,使用者就會重新下載最新的檔案
備註:
Time-restricted
max-age = 0
- 表示快取的資源立即過期,並且每次請求都需要向原伺服器進行確認。這保證了每次請求都獲得最新的資料。再搭配上
Etag來使用,就可以保證只會下載到最新的 Response。 - 強調的是資源立即過期並且每次都需要驗證
max-age: 300
- 設定 cache 的最大時間,超過這個時間儲存被認為是過期(秒)Expires。
- 在 client 端 cache 5 分鐘
s-maxage: 300
Cache-control: max-age=3600, s-maxage=86400
- s-maxage 的 s 表示 shared 的意思,專門用於共用快取(如 CDN)。
上面設定針對 client(browser) 的有效時間會是一小時,但 CDN 則會是一天。
must-revalidate
- 表示如果檔案過期後,瀏覽器一定要先向 server 詢問。
- 防止在斷網、Server 掛掉時,繼續使用 stale responses 。
通常,must-revalidate 與 一起使用 max-age。
Cache-Control: max-age=604800, must-revalidate
此範例將允許瀏覽器使用快取的資源長達一年,之後必須透過伺服器重新驗證
Revalidated
max-age = 1, stale-while-revalidate= 60
在發送請求前會先檢查 max-age 來判斷 cache 是否已經過期,
- 如果還沒過期,就直接拿瀏覽器的快取(不會實際像 server 發出請求);
- 但若它判斷已經過期,同時有設定 stale-while-revalidate 的時間(且在該時間區間內),會判定該 cache 已經過期,雖然如此,會先拿原本的 cache 來作為回應,但「同時」又在背景向伺服器發送請求(revalidation request),以此產生新的 cache 供未來使用。
- 內容不會改變的檔案,完全不需要 revalidate
情境:
- 0 ~ 1 秒時:由於使用 max-age=1,因此在這段時間內向伺服器發送請求時,瀏覽器會直接取用保存過的 cache 當作回應(fulfill browser request),而不會實際向 server 發送請求。
- 1 ~ 60 秒時:由於超過了 max-age 定義的時間,因此 cache 已經過期,但因為有使用 stale-while-revalidate ,因此瀏覽器仍會先以 cache 當作請求的回應(fulfill browser request),但同時在背景向伺服器查詢有無新的畫面。 如果有,就會更新畫面上的資料。
- 61 秒之後:超過了 max-age 和 stale-while-revalidate 所定義的時間,因此 cache 已經過期,也不會使用 swr 的機制,而是直接向伺服器發送請求,取得伺服器最新的回應
備註: private & public
- private: 此 cache 是私有的,只有 client (browser) 可以儲存 cache , 而不能由中繼代理程式(如 CDN 或代理)快取,常用於個人化敏感資料或像是 api response。
- public: 此 cache 為公有共享的,中間傳輸的 ISP, proxy 及 CDN 服務商都可以儲存 cache,常用於公用的靜態檔案,像是 js, css 及圖片等 asset。
- e.g. public, max-age=14400

- e.g. public, max-age=14400
當 Cache 過期時
- 當 cache 過期了,加上如果資源(e.g. Google 的 Logo)一年後仍然沒有變,瀏覽器可以繼續使用已經快取的圖片,而伺服器只需告知瀏覽器快取的圖片仍然有效,無需重新下載。
- 這裡可以透過
Last-Modified與ETag搭配使用
ETag 標頭的優先級高於 Last-Modified
Last-Modified
- 表示資源最後一次修改的時間
- 在 server 回傳
Last-Modified的 HTTP Header 後,瀏覽器在後續的 Request Header 中都會自動帶入If-Modified-Since的欄位,這個欄位的值會是上一次發送請求時Last-Modified回傳的值。 - server 即可根據
If-Modified-SinceHeader 的時間來決定要不要回傳新的內容給瀏覽器。 - 當 client 端帶著If-Modified-Since 時, server 透過 Last-Modified 去判斷是否cache 為最新的,如果是就會回傳 304 http code
- Last-Modified: 出現在伺服器回應給瀏覽器的 response header 裡,告訴瀏覽器這個檔案上次更改是什麼時候。
If-Modified-Since: 出現在瀏覽器發出請求的 resquest header 裡,用來跟伺服器確認檔案在某個時間點後是不是有經過更改。
上述提到的是檔案是否被編輯,但編輯時間其實是電腦上的檔案修改時間。 即使開啟檔案未作更改,儲存後編輯時間也會更新,內容卻可能未變。
相對於編輯時間 ,使用「檔案內容是否更改」來判斷是否更新快取更理想。 Etag Header 正是基於此,它像是檔案內容的哈希值,相同內容產生相同 hash 值,不同則產生不同 hash 值,以此判斷內容是否變更。
ETag
ETag(Entity Tag)是一種唯一標識符,用於識別一個資源的特定版本。- 在 server 回傳
Etag的 HTTP Header 後,瀏覽器在後續的 Request Header 中都會自動帶入If-None-Match的欄位,這個欄位的值會是上一次發送請求時Etag回傳的值。 - 主要就是針對 revalidate
- server 即可根據
If-None-MatchHeader 的值來決定要不要回傳新的內容給瀏覽器。
舉例: 第一個 Response 可能是這樣:
Cache-Control: max-age=0
Etag: abcdef
重新整理一次,瀏覽器發出這樣的 Request:
If-None-Match: abcdef
如果檔案沒有變動,Server 就會回傳:304 Modified,有變動的話就會回傳新的檔案並且更新 Etag。如果是使用這種方式,其實就是「每一次造訪頁面都會發送一個 Request 去確認有沒有新的檔案,有的話就下載更新,沒有的話沿用快取裡的」。

網頁快取策略
- 靜態資源(圖片): 使用
Cache-Control: max-age=xx - 有版本的檔案: 使用
Cache-Control: max-age=31536000, immutable - 可能變更的非版本化檔案: 使用
Cache-Control: max-age=604800, stale-while-revalidate=86400
ETag: "<file-hash-generated-by-server>" - 網頁:
- 網站首頁雖然也不常會變動,希望只要一變動,使用者就能夠馬上看到變化:
Cache-Control: max-age:300 - 個人畫頁面:
Cache-Control: max-age:300, private
- 網站首頁雖然也不常會變動,希望只要一變動,使用者就能夠馬上看到變化:

Next.js 快取策略
- ISR 頁面: s-maxage=60, stale-while-revalidate
- 動態頁面: private, no-cache, no-store, max-age=0, must-revalidate
- 有版本的資源: public, max-age=31536000, immutable
Memory Cache vs Disk Cache
參考: https://ithelp.ithome.com.tw/articles/10276125

當深入探討 Chrome 瀏覽器的快取機制時,你可能會偶爾遇到除了常見的from disk cache外,還有一種叫做from memory cache的標記。
這 個 memory cache 可能不是所有瀏覽器都有的,可以視為 Chrome 的一項特殊實作。
簡單來說,memory cache 是將資源存儲在內存(RAM)中,這讓存取速度更快於儲存在硬碟的 disk cache。 然而,這種快取的資料是揮發性的,一旦關閉瀏覽器,所有在內存中的資料就會消失。
總結來說,Memory Cache 提供了速度上的優勢,適合快速存取的情況,而 Disk Cache 提供了持久性和大容量的優勢,適合長期和大量數據的存儲。
從前端開發的視角來看,了解瀏覽器如何選擇哪些資源放入 memory cache 是個有趣但複雜的話題。 雖然沒有明確的規則,我們知道使用了 resource hint 如 preload 和 prefetch 的資源更可能被存到 memory cache。 當然,由於記憶體空間遠小於硬碟,不可能所有資源都放入 memory cache,這需要一個有效的空間管理策略,幸好這部分瀏覽器都幫我們處理好。
優先順序: memory cache -> service worker cache -> disk cache (browser cache)
其他
用 cURL 檢查 HTTP Header
curl -I xxx

另外一個方式就是透過好用的 CLI Tool cURL,他除了可以獲得從 Server 端傳送而來的 HTTP Header 之外,也可以自己取組出想要送到 Server 端的 HTTP Header
在網頁中快速瀏覽 cache-control 設定

需注意 stale-while-revalidate 在 safari 上面並不支援。 會默默地忽略該配置值,並使用 max-age 搭配 ETag, 讓瀏覽器在 max-age 過期後,使用 ETag 進行異步重驗證,以確保資源的新鮮度。
https://caniuse.com/?search=stale-while-revalidate
結論

參考資料:
- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control
- https://blog.camel2243.com/2018/09/23/http-http-header,-cache-control-expires-用法說明/
- https://blog.techbridge.cc/2017/06/17/cache-introduction/
- https://www.zhihu.com/question/64201378
- https://www.cloudflare.com/zh-tw/learning/cdn/glossary/what-is-cache-control/
- https://pjchender.dev/webdev/note-http-cache/
- https://medium.com/starbugs/%E6%80%8E%E9%BA%BC%E7%B6%B2%E9%A0%81%E6%94%B9%E5%AE%8C%E9%82%84%E6%98%AF%E9%8C%AF%E7%9A%84-%E4%B8%80%E6%AC%A1%E6%90%9E%E6%87%82-http-cache-%E6%A9%9F%E5%88%B6-a39a421df6c9
- https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Caching
- https://ithelp.ithome.com.tw/articles/10276125
- https://simonhearne.com/2022/caching-header-best-practices
- https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html#stale-content