跳至主要內容
Skip to content

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=31536000

Expires(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 GMT

4.3 ETag vs Last-Modified

特性ETagLast-Modified
精確度高(內容雜湊)低(秒級時間)
計算成本較高較低
優先級
適用場景內容敏感一般檔案

五、 強快取 vs 協商快取

5.1 比較表

特性強快取協商快取
是否發請求❌ 不發✅ 發送
狀態碼200 (from cache)304 Not Modified
控制標頭Cache-Control, ExpiresETag, 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=60

8.2 強制刷新

操作Windows/LinuxMac效果
正常刷新F5Cmd+R使用快取
強制刷新Ctrl+F5Cmd+Shift+R忽略快取
清除快取DevToolsDevTools完全清除

總結

類型標頭是否請求狀態碼
強快取Cache-Control, Expires200 (from cache)
協商快取ETag, Last-Modified304

> **最佳策略**:

  • 帶 hash 的靜態資源:max-age=1y
  • HTML:no-cache(每次驗證)
  • API:根據資料敏感度設定

進階挑戰

  1. 查看 Chrome DevTools 的 Network 面板,區分 disk cachememory cache
  2. 實作一個 Service Worker,實現離線優先策略。
  3. 研究 stale-while-revalidate 策略,了解其優勢。

延伸閱讀與資源