HTTP 快取總覽:強快取 vs 協商快取
快取是提升 Web 效能的關鍵技術。正確使用快取可以減少網路請求、降低伺服器負擔、加速頁面載入。
一、 為什麼需要快取?
1.1 沒有快取的問題
1.2 快取帶來的好處
| 優勢 | 說明 |
|---|---|
| 減少延遲 | 本地讀取比網路快 |
| 節省流量 | 不需重複下載 |
| 降低負載 | 減少伺服器壓力 |
| 離線存取 | Service Worker 可離線使用 |
二、 快取類型
2.1 快取架構
2.2 私有 vs 共享快取
| 類型 | 說明 | 範例 |
|---|---|---|
| 私有快取(Private) | 專屬單一用戶 | 瀏覽器快取 |
| 共享快取(Shared) | 多用戶共用 | CDN(Content Delivery Network,內容傳遞網路)、代理 |
http
# 私有快取
Cache-Control: private, max-age=3600
# 共享快取
Cache-Control: public, max-age=86400三、 強快取(Strong Cache)
3.1 概念
瀏覽器不發送請求,直接使用本地快取:
3.2 實現方式
Cache-Control(HTTP/1.1)
http
Cache-Control: max-age=31536000Expires(HTTP/1.0,已過時)
http
Expires: Wed, 21 Oct 2025 07:28:00 GMT> `Cache-Control` 優先級高於 `Expires`。現代開發請使用 `Cache-Control`。
3.3 判斷流程
四、 協商快取(Negotiated Cache)
4.1 概念
瀏覽器發送請求詢問伺服器,資源是否有變更:
4.2 實現方式
ETag + If-None-Match
http
# 回應
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# 後續請求
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"Last-Modified + If-Modified-Since
http
# 回應
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
# 後續請求
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT4.3 ETag vs Last-Modified
| 特性 | ETag | Last-Modified |
|---|---|---|
| 精確度 | 高(內容雜湊) | 低(秒級時間) |
| 計算成本 | 較高 | 較低 |
| 優先級 | 高 | 低 |
| 適用場景 | 內容敏感 | 一般檔案 |
五、 強快取 vs 協商快取
5.1 比較表
| 特性 | 強快取 | 協商快取 |
|---|---|---|
| 是否發請求 | ❌ 不發 | ✅ 發送 |
| 狀態碼 | 200 (from cache) | 304 Not Modified |
| 控制標頭 | Cache-Control, Expires | ETag, Last-Modified |
| 速度 | 最快 | 較快 |
| 適用場景 | 靜態資源 | 動態內容 |
5.2 完整流程
六、 Express 實作
6.1 靜態資源快取
javascript
const express = require("express");
const path = require("path");
const app = express();
// 帶 hash 的檔案:長期快取
app.use(
"/assets",
express.static("dist/assets", {
maxAge: "1y", // 1 年
immutable: true,
})
);
// 不帶 hash 的檔案:短期快取
app.use(
express.static("public", {
maxAge: "1h",
etag: true,
lastModified: true,
})
);6.2 API 回應快取
javascript
// 不快取敏感資料
app.get("/api/user", (req, res) => {
res.set("Cache-Control", "no-store");
res.json(userData);
});
// 短期快取公開資料
app.get("/api/products", (req, res) => {
res.set("Cache-Control", "public, max-age=60");
res.json(products);
});
// 協商快取
const etag = require("etag");
app.get("/api/config", (req, res) => {
const data = JSON.stringify(config);
const hash = etag(data);
res.set("ETag", hash);
res.set("Cache-Control", "no-cache"); // 每次都驗證
if (req.headers["if-none-match"] === hash) {
return res.status(304).end();
}
res.json(config);
});七、 快取儲存位置
7.1 Memory Cache vs Disk Cache
| 類型 | 說明 | 速度 | 持久性 |
|---|---|---|---|
| Memory Cache | 記憶體快取 | 極快 | 關閉頁籤消失 |
| Disk Cache | 硬碟快取 | 較快 | 持久 |
瀏覽器自動決定:
- 小型、頻繁存取 → Memory
- 大型、較少存取 → Disk
7.2 Service Worker Cache
javascript
// 安裝時快取
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open("v1").then((cache) => {
return cache.addAll(["/", "/styles.css", "/app.js"]);
})
);
});
// 請求時從快取回應
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});八、 常見問題
8.1 為什麼更新後還是舊內容?
javascript
// 問題:index.html 被過度快取
Cache-Control: max-age=86400
// 解決:HTML 不快取或短期快取
Cache-Control: no-cache
// 或
Cache-Control: max-age=608.2 強制刷新
| 操作 | Windows/Linux | Mac | 效果 |
|---|---|---|---|
| 正常刷新 | F5 | Cmd+R | 使用快取 |
| 強制刷新 | Ctrl+F5 | Cmd+Shift+R | 忽略快取 |
| 清除快取 | DevTools | DevTools | 完全清除 |
總結
| 類型 | 標頭 | 是否請求 | 狀態碼 |
|---|---|---|---|
| 強快取 | Cache-Control, Expires | ❌ | 200 (from cache) |
| 協商快取 | ETag, Last-Modified | ✅ | 304 |
> **最佳策略**:
- 帶 hash 的靜態資源:
max-age=1y - HTML:
no-cache(每次驗證) - API:根據資料敏感度設定
進階挑戰
- 查看 Chrome DevTools 的 Network 面板,區分
disk cache和memory cache。 - 實作一個 Service Worker,實現離線優先策略。
- 研究
stale-while-revalidate策略,了解其優勢。