跳至主要內容
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, Expires❌200 (from cache)
協商快取ETag, Last-Modified✅304

> **最佳策略**:

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

進階挑戰 ​

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

延伸閱讀與資源 ​