HTTP 快取完全指南:強制快取、協商快取與 CDN 快取策略全解析
Web/SEO趙婉清
HTTP 快取:效能優化的第一道防線
合理設定快取,可以讓二次造訪零網路請求,載入時間從秒級降到毫秒級。
| 快取類型 | 機制 | 是否請求伺服器 | 速度 |
|---|---|---|---|
| 強制快取 | Cache-Control / Expires | 否 | 最快(0ms) |
| 協商快取 | ETag / Last-Modified | 是(條件請求) | 快(304 回應) |
| 無快取 | no-cache / no-store | 是(完整請求) | 最慢 |
一、強制快取
Cache-Control 指令
Cache-Control: max-age=31536000, immutable
| 指令 | 作用 | 適用場景 |
|---|---|---|
max-age=N |
快取 N 秒 | 靜態資源 |
s-maxage=N |
CDN 快取 N 秒(覆蓋 max-age) | CDN 場景 |
immutable |
即使過期也不重新驗證 | 含雜湊值的檔名 |
public |
允許中間快取 | CDN |
private |
僅瀏覽器快取 | 使用者個人化內容 |
no-cache |
每次使用前必須驗證 | HTML 頁面 |
no-store |
完全不快取 | 敏感資料 |
must-revalidate |
過期後必須驗證 | API 回應 |
stale-while-revalidate=N |
過期後先回傳舊值,背景更新 | 漸進更新 |
Expires(舊標準)
Expires: Thu, 02 Jun 2027 00:00:00 GMT
優先順序:Cache-Control: max-age > Expires。
二、協商快取
ETag / If-None-Match
# 首次請求
GET /api/data
→ 200 OK
ETag: "abc123"
Cache-Control: max-age=0
# 再次請求(附條件)
GET /api/data
If-None-Match: "abc123"
→ 304 Not Modified (無回應主體,節省頻寬)
Last-Modified / If-Modified-Since
# 首次請求
GET /style.css
→ 200 OK
Last-Modified: Mon, 01 Jun 2026 12:00:00 GMT
# 再次請求
GET /style.css
If-Modified-Since: Mon, 01 Jun 2026 12:00:00 GMT
→ 304 Not Modified
ETag vs Last-Modified
| 維度 | ETag | Last-Modified |
|---|---|---|
| 精度 | 位元組級 | 秒級 |
| 效能 | 需計算雜湊 | 直接讀取檔案時間 |
| 可靠性 | 高 | 低(1 秒內多次修改會遺失) |
| 分散式 | 需保證雜湊一致 | 天然一致 |
建議:優先使用 ETag。
三、快取策略矩陣
HTML 頁面
Cache-Control: no-cache
HTML 是入口檔案,必須每次驗證,確保使用者拿到最新版本。
含雜湊值的靜態資源
Cache-Control: max-age=31536000, immutable
檔名含雜湊值(如 app.abc123.js),內容變化時雜湊值變化 → URL 變化 → 新請求。舊 URL 永遠不會變,可以快取一年並標記 immutable。
不含雜湊值的靜態資源
Cache-Control: max-age=86400, stale-while-revalidate=604800
過期後先回傳舊值,背景靜默更新,使用者無感知。
API 回應
Cache-Control: private, max-age=0, must-revalidate
API 回應通常含使用者資料,private 禁止 CDN 快取,must-revalidate 確保過期後不回傳舊值。
四、Vary 標頭:快取鍵的擴展
Vary: Accept-Encoding, Accept-Language
Vary 告訴快取:除了 URL,還要根據這些請求標頭的值區分快取版本。
| Vary 值 | 含義 | 場景 |
|---|---|---|
Accept-Encoding |
按壓縮方式快取 | gzip vs br |
Accept-Language |
按語言快取 | 多語言網站 |
Cookie |
按 Cookie 快取 | 個人化頁面 |
* |
每個請求獨立快取 | 等於不快取 |
注意:Vary: Cookie 會導致快取命中率極低,慎用。
五、CDN 快取策略
回源控制
# CDN 快取 1 天,瀏覽器快取 5 分鐘
Cache-Control: public, s-maxage=86400, max-age=300
| 場景 | s-maxage | max-age | 說明 |
|---|---|---|---|
| 靜態資源 | 31536000 | 31536000 | CDN 和瀏覽器都長期快取 |
| API 回應 | 300 | 0 | CDN 快取 5 分鐘,瀏覽器不快取 |
| HTML | 60 | 0 | CDN 快取 1 分鐘,瀏覽器不快取 |
| 個人化內容 | — | 0 | CDN 不快取,瀏覽器不快取 |
CDN 快取重新整理
| 方式 | 速度 | 適用 |
|---|---|---|
| URL 預熱 | 快 | 新內容上線 |
| 全量重新整理 | 慢 | 緊急修復 |
| 目錄重新整理 | 中 | 批次更新 |
| Cache-Control: no-cache | 自動 | API 場景 |
Stale-While-Revalidate 實戰
Cache-Control: max-age=60, stale-while-revalidate=86400
第 0 秒:請求 → 快取 MISS → 回源 → 回傳新鮮值
第 30 秒:請求 → 快取 HIT → 回傳快取值
第 90 秒:請求 → 快取 STALE → 立即回傳舊值 + 背景回源
第 91 秒:背景回源完成 → 快取更新
第 120 秒:請求 → 快取 HIT → 回傳新值
效果:使用者永遠不等待回源,背景靜默更新。
六、快取破壞策略
內容雜湊(推薦)
<!-- 建置時生成雜湊值 -->
<script src="/app.abc123.js"></script>
<link href="/style.def456.css">
// webpack 設定
output: {
filename: '[name].[contenthash:8].js',
}
版本號查詢參數
<!-- 簡單但 CDN 可能不快取 ?v= 參數 -->
<script src="/app.js?v=2.0.0"></script>
問題:某些 CDN 忽略查詢參數,導致快取不更新。
時間戳(不推薦)
<!-- 每次都變,完全破壞快取 -->
<script src="/app.js?t=1717315200"></script>
七、Service Worker 快取
Cache First 策略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});
Stale While Revalidate 策略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('dynamic').then(cache =>
cache.match(event.request).then(cached => {
const fetchPromise = fetch(event.request)
.then(response => {
cache.put(event.request, response.clone());
return response;
});
return cached || fetchPromise;
})
)
);
});
快取策略選擇
| 策略 | 優先來源 | 適用 |
|---|---|---|
| Cache First | 快取 | 字型、圖片 |
| Network First | 網路 | API、HTML |
| Stale While Revalidate | 快取+背景更新 | CSS、JS |
| Network Only | 網路 | 非 GET 請求 |
| Cache Only | 快取 | 離線頁面 |
八、偵錯快取
Chrome DevTools
- DevTools → Network 面板
- 查看
Size欄:from disk cache:磁碟快取命中from memory cache:記憶體快取命中- 實際大小:未命中
- 查看
Age回應標頭:快取已存在的時間 - 勾選 Disable cache:停用快取(DevTools 開啟時)
curl 驗證
# 查看回應標頭
curl -I https://example.com/style.css
# 送出條件請求
curl -I -H 'If-None-Match: "abc123"' https://example.com/style.css
# 304 表示協商快取命中
# 模擬不同 Accept-Encoding
curl -I -H 'Accept-Encoding: br' https://example.com/style.css
九、最佳實踐清單
- HTML:no-cache,確保入口始終最新
- 含雜湊值的 JS/CSS:max-age=1 年 + immutable
- API:must-revalidate,過期後必須驗證
- CDN:s-maxage 控制回源頻率
- Vary: Accept-Encoding,區分壓縮方式
- 使用 ETag 而非 Last-Modified
- stale-while-revalidate 實現無感更新
- Service Worker 補充離線能力
#HTTP缓存#Cache-Control#ETag#CDN#性能优化