跳至主要內容

CDN 快取策略設計:讓你的網站快到飛起來

CDN 快取策略設計:讓你的網站快到飛起來

CDN(Content Delivery Network,內容傳遞網路)是現代 Web 效能優化的核心工具。一個設計良好的快取策略,可以讓靜態資源的回應時間從幾百毫秒降到幾毫秒,大幅改善使用者體驗。這篇文章系統性地介紹 CDN 快取策略的設計思路與實作細節。

CDN 的基本運作原理

使用者 → CDN 邊緣節點(快取命中)→ 直接回傳
使用者 → CDN 邊緣節點(快取未命中)→ 源站 → 快取並回傳

CDN 在全球各地部署邊緣節點,使用者的請求會被路由到最近的節點。如果該節點有快取,就直接回傳;否則才回源站取得並快取。

HTTP 快取標頭

快取控制的核心是 HTTP 標頭:

Cache-Control

# 快取 1 年(適用於有 hash 的靜態資源)
Cache-Control: public, max-age=31536000, immutable

# 快取 1 小時,允許過期後驗證
Cache-Control: public, max-age=3600, stale-while-revalidate=86400

# 不快取(適用於 API 回應)
Cache-Control: no-store

# 每次都驗證(適用於 HTML 頁面)
Cache-Control: no-cache

ETag 與 Last-Modified

# 伺服器回應
ETag: "abc123"
Last-Modified: Wed, 01 Jan 2025 00:00:00 GMT

# 客戶端重新驗證
If-None-Match: "abc123"
If-Modified-Since: Wed, 01 Jan 2025 00:00:00 GMT

# 如果內容未變更,伺服器回傳 304
HTTP/1.1 304 Not Modified

不同資源類型的快取策略

靜態資源(有 content hash)

最佳策略是永久快取。現代打包工具(Vite、Webpack)會在檔名加入內容 hash:

main.abc123.js   → 一旦發布就不會變動
style.def456.css → 有更新就產生新 hash,新 URL
# Nginx 設定
location ~* \.(js|css|png|jpg|webp|woff2)$ {
    # 符合 hash 模式的靜態資源
    if ($uri ~* "\.[a-f0-9]{8,}\.(js|css)$") {
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
}

HTML 頁面

HTML 通常包含對靜態資源的引用,不應長期快取:

location ~* \.html$ {
    add_header Cache-Control "public, no-cache";
    add_header ETag $request_id;
}

API 回應

依照資料的更新頻率決定:

// Express.js 範例
app.get('/api/products', (req, res) => {
  // 產品列表:快取 5 分鐘,允許過期後繼續服務
  res.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=3600');
  res.json(products);
});

app.get('/api/stock/:id', (req, res) => {
  // 庫存資訊:不快取,需要即時
  res.set('Cache-Control', 'no-store');
  res.json(stock);
});

Cloudflare 快取設定

Cloudflare 是最普及的 CDN 服務,提供靈活的快取規則:

Page Rules / Cache Rules

# 靜態資源:快取等級設為 Cache Everything
URL 模式: *.example.com/assets/*
快取等級: Cache Everything
邊緣快取 TTL: 1 個月

# API:繞過快取
URL 模式: *.example.com/api/*
快取等級: Bypass

Cloudflare Workers 精細控制

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const url = new URL(request.url);

  // 靜態資源:使用 CDN 快取
  if (url.pathname.startsWith('/assets/')) {
    const response = await fetch(request, {
      cf: {
        cacheTtl: 86400 * 365,
        cacheEverything: true,
      },
    });
    return response;
  }

  // API:加上版本標頭,短暫快取
  if (url.pathname.startsWith('/api/public/')) {
    const response = await fetch(request, {
      cf: {
        cacheTtl: 60,
        cacheEverything: true,
      },
    });
    return response;
  }

  return fetch(request);
}

Cache Purge(清除快取)

當內容更新時,需要主動清除 CDN 快取:

# Cloudflare API:清除特定 URL
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
  -H "Authorization: Bearer {api_token}" \
  -H "Content-Type: application/json" \
  --data '{"files": ["https://example.com/api/products"]}'

# 清除所有快取(謹慎使用)
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
  -H "Authorization: Bearer {api_token}" \
  -H "Content-Type: application/json" \
  --data '{"purge_everything": true}'

在 CI/CD 中自動清除

# .github/workflows/deploy.yml
- name: Deploy
  run: npm run build && rsync -av dist/ server:/var/www/

- name: Purge CDN Cache
  run: |
    curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/purge_cache" \
      -H "Authorization: Bearer $CF_API_TOKEN" \
      -H "Content-Type: application/json" \
      --data '{"files": ["https://example.com/"]}'
  env:
    CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
    CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}

stale-while-revalidate 策略

這是一個非常實用的快取策略,讓使用者不需要等待重新驗證:

Cache-Control: max-age=60, stale-while-revalidate=3600
  • 第 0-60 秒:直接用快取,不詢問伺服器
  • 第 61-3660 秒:回傳過期的快取(不讓使用者等),同時在背景更新
  • 超過 3660 秒:等待伺服器回應

快取命中率監控

// 在回應標頭中加入快取狀態
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const cacheStatus = res.getHeader('cf-cache-status') ||
                        res.getHeader('x-cache') || 'UNKNOWN';
    metrics.increment('cdn.request', {
      cache_status: cacheStatus,
      path: req.path,
      status: res.statusCode,
    });
  });
  next();
});

快取策略決策樹

是否包含個人化資料?
├── 是 → Cache-Control: private 或 no-store
└── 否
    ├── 是否是靜態資源(有 hash)?
    │   ├── 是 → max-age=31536000, immutable
    │   └── 否
    │       ├── 更新頻率?
    │       ├── 即時 → no-store
    │       ├── 分鐘級 → max-age=60, stale-while-revalidate
    │       └── 小時/天級 → max-age=3600+, no-cache

總結

良好的 CDN 快取策略能大幅提升網站效能,降低源站負載。核心原則是:靜態資源永久快取,HTML 不快取或短暫快取,API 根據資料時效性決定。搭配 content hash 的靜態資源和自動化的 cache purge 流程,可以在效能與即時性之間取得最佳平衡。

分享這篇文章