- date
- entry
- 019
- topic
- infrastructure
- rev
- —
靜態站要三種快取策略,不是一種
用一條 max-age 涵蓋全站,只有兩種結局:設太長,改了東西線上沒反應;設太短,每個請求都回源,CDN 等於白裝。判準其實只有一句話。
這個檔案的名字,會不會隨著內容改變?
答案是「會」的檔案可以永久快取——因為內容變了它就是另一個網址,舊的那份沒人會再要。答案是「不會」的檔案必須每次確認——因為同一個網址明天可能是不同的內容。
靜態站按這條線切下去正好三類:
| 類別 | 檔名會變? | Cache-Control |
|---|---|---|
| 帶雜湊的資產(JS/CSS/圖) | 會 | public, max-age=31536000, immutable |
| HTML | 不會 | public, max-age=0, must-revalidate |
| feed/sitemap | 不會 | public, max-age=3600 |
三段 sync
aws s3 sync 的 --cache-control 是整批套用的,所以要分三次跑,用
--exclude 與 --include 把檔案切開:
# 1. 帶雜湊的資產:一年,永不重驗
aws s3 sync dist/ "s3://$BUCKET/" --delete \
--exclude "*.html" --exclude "*.xml" \
--cache-control "public, max-age=31536000, immutable"
# 2. HTML:可以存,但每次都要問
aws s3 sync dist/ "s3://$BUCKET/" --delete \
--exclude "*" --include "*.html" \
--cache-control "public, max-age=0, must-revalidate" \
--content-type "text/html; charset=utf-8"
# 3. feed 與 sitemap:一小時
aws s3 sync dist/ "s3://$BUCKET/" \
--exclude "*" --include "*.xml" \
--cache-control "public, max-age=3600"
三段的過濾規則互斥,所以每個檔案只會被其中一段處理到,不會互相覆寫。順序不影響結果。
immutable 跟 max-age 差在哪
一年的 max-age 已經很長了,那 immutable 還多做什麼?差別在使用者按重新整理的時候。
沒有 immutable 時,重新整理會讓瀏覽器對所有資源發出重新驗證請求——就算它們還在有效期內。這些請求多半拿到 304,沒有傳輸資料,但往返時間還是要付。immutable 明確告訴瀏覽器:這個網址的內容永遠不會變,連問都不用問。
前提是檔名真的帶雜湊。如果你的 app.js 名字固定、內容會換,套上
immutable 就等於把使用者鎖在舊版一年,而且沒有辦法從伺服器端解除——只能改網址。
max-age=0 不是「不要快取」
這是最常被誤解的一條。max-age=0, must-revalidate 的意思是「可以存下來,但每次使用前都要問一次還新不新」,不是「不准存」。
差別在於配上 ETag 之後,那個「問一次」多半會得到 304 Not Modified:沒有傳輸 HTML 內容,只有一次往返。對一份 14 KB 的頁面,這省下的是絕大部分的位元組。
真的要禁止快取是 no-store,那是給含個人資料的動態頁面用的。靜態站的 HTML 不需要它。
第二段那個 --content-type "text/html; charset=utf-8" 不要省。S3 猜得出
.html 是 HTML,但不會自己補 charset,而 HTTP header 的優先權高於 HTML 裡的
<meta charset>。中文站少了它,在某些瀏覽器設定下就是一頁亂碼。
feed 為什麼是一小時
feed 和 sitemap 的檔名固定,理論上該跟 HTML 一樣每次重驗。我給它一小時,理由是讀者不需要秒級的新鮮度——RSS 閱讀器本來就是幾十分鐘輪詢一次,搜尋引擎抓 sitemap 更慢。
一小時的代價是:發文之後,最多一小時內訂閱者才會看到。這對一個部落格是可以接受的;如果是新聞站就不行。快取時間該由「多晚看到會有問題」決定,不是由檔案類型決定。
invalidation 不是快取策略
CloudFront 的 invalidation 很好用,但它是逃生口,不是設計的一部分。它有三個成本:要錢(超過免費額度之後)、要時間(幾十秒到幾分鐘)、而且它清的是 CDN,清不掉使用者瀏覽器裡的那份。
最後一點是關鍵。如果 HTML 被設成快取一天,那麼即使你 invalidate 了 CDN,昨天來過的使用者今天看到的還是舊頁面,而你在自己的無痕視窗裡完全看不到這個問題。
所以 HTML 那條 max-age=0 才是主角,invalidation 只是讓 CDN 這一層立刻跟上。
也因為 invalidation 有延遲,部署腳本在比對全站內容之前要先等一段時間——太早驗證會讀到還沒失效的邊緣節點,得到假的失敗。
如果只記得一件事
快取策略是由檔名的性質決定的,不是由你希望它多快更新決定的。 檔名帶雜湊就快取到永遠,檔名固定就每次確認。中間那些「設個十分鐘應該還好」的直覺數字,通常同時拿到兩邊的缺點。
修訂紀錄
- 首次發布