• 從 RTT 與連線建立的角度解釋 CDN 為什麼能加速,以及它加速的到底是哪幾段時間。

  • 認識 CDN 的基本架構:PoP / 邊緣節點、Anycast、origin shield、cache key。

  • 正確搭配 、、 與清除(purge)機制,讓 HTML 也能安全地在邊緣快取。

  • 從回應標頭(、、、)判讀命中狀況,並估算快取命中率。

  • 了解影像 CDN、邊緣運算、Early Hints 等進階功能對 LCP 與 TTFB 的幫助。

網購如果每件商品都要從國外原廠工廠直接寄來,不管快遞多努力,跨海的時間就是省不掉。CDN 就是在每個城市設倉庫:熱門商品先囤在離你最近的倉庫(邊緣節點),下單後幾小時就到;倉庫沒有的才向原廠調貨(回源,origin fetch),而且調到之後會留一份在倉庫給下一個人。

距離就是時間

算過:一次全新的 HTTPS 連線到拿到第一個位元組,TCP + TLS 1.3 需要約 3 個 RTT,再加上 TCP 慢啟動,前幾十 KB 還要多幾個 RTT。RTT 的下限由物理決定:光在光纖中約每秒 20 萬公里,每 1,000 公里的往返至少 10 ms,實際路由再加上交換與排隊延遲,通常是理論值的 1.3–2 倍。

L4.2cm C2.6cm C2.6cm C3.4cm 使用者(台北)→ 伺服器 & 距離(約) & 實測 RTT(約) & 3 RTT 建立連線
台北市內機房 / 邊緣節點 & \(<\) 50 km & 2–10 ms & 6–30 ms
東京 & 2,100 km & 30–45 ms & 90–135 ms
新加坡 & 3,200 km & 50–70 ms & 150–210 ms
美國西岸 & 11,000 km & 130–160 ms & 390–480 ms
歐洲(法蘭克福) & 9,400 km(經美國或中東) & 220–280 ms & 660–840 ms

max width=

同一份 60 KB 的 HTML,直連遠端與經由 CDN 的時間比較(示意)。即使未命中,邊緣節點與 origin 之間長期保持的暖連線也省下了 TCP / TLS 交握與慢啟動。
  1. 縮短「與使用者之間」的 RTT:TCP / TLS 交握、慢啟動、每個請求的往返,全部改成與 10 ms 外的邊緣節點進行。這對所有回應都有效,包含不能快取的動態內容。

  2. 在邊緣儲存回應:命中時完全不必回源,TTFB 只剩邊緣的處理時間(通常 \(<\) 50 ms)。

  3. 優化邊緣到 origin 的路徑:長期保持的暖連線(不用重新交握)、私有骨幹網路、請求合併(request coalescing:多個相同的未命中只回源一次)。

架構:PoP、Anycast 與 origin shield

max width=

CDN 的多層快取架構。
  • PoP(Point of Presence)/ 邊緣節點:分布在各城市機房的伺服器群,負責終結 TLS、回應快取內容、壓縮、執行邊緣程式。大型 CDN 有 300+ 個城市的 PoP。

  • Anycast:所有 PoP 宣告同一個 IP,網際網路路由(BGP)自然把封包送到最近的 PoP;另一種做法是 GeoDNS(依解析者位置回不同 IP)。這也是為什麼 DNS 查詢後你會看到 CDN 的 IP 而不是自己伺服器的。

  • Origin shield / tiered cache:在邊緣與 origin 之間再加一層區域快取。沒有它,每個 PoP 第一次未命中都各自回源;有了它,同一區的 PoP 共享一份,命中率上升、origin 負載下降。

  • Cache key:CDN 用來辨認「同一個資源」的鍵,預設是 host + path + query string,可設定忽略某些參數、加入裝置類型、語言、國家、特定 cookie。鍵越細,命中率越低。

讓 CDN 正確快取:標頭與清除

告訴 CDN 該存多久

L4.4cm L9.1cm 標頭 & 說明
& 標準指令,只影響共享快取;瀏覽器用 。最常見的組合:。
& RFC 9213「目標式快取控制」:只給 CDN 看的一組完整指令,會覆蓋 ,且 CDN 通常不會把它轉給瀏覽器。各家還有專屬版本:、Fastly 的 、Akamai 的 。
/ & 對 CDN 特別重要:過期後先回舊版、同時背景回源更新,使用者永遠不必等回源;origin 掛掉時繼續服務。
& CDN 會為每個 Vary 值組合存一份。 正常; / 幾乎等於不快取(多數 CDN 會直接 BYPASS)。
& 帶有 Set-Cookie 的回應多數 CDN 預設不快取(避免把某人的 session 發給別人)。靜態資源請不要帶 cookie。
/ & 為回應貼標籤(如 、),之後可依標籤一次清除相關頁面。

HTTP/2 200
content-type: text/html; charset=utf-8
cache-control: public, max-age=0, must-revalidate
cdn-cache-control: max-age=3600, stale-while-revalidate=86400, stale-if-error=604800
cache-tag: article-8812, author-42, home
etag: "a1b2c3"
vary: Accept-Encoding

清除(purge)與版本化

快取越久,命中率越高,但內容更新時就得主動清除。三種做法搭配使用:

  1. 版本化 URL(雜湊檔名):靜態資源永遠不需要清除,CDN 給一年。

  2. 標籤清除(purge by tag):文章更新時清掉 標籤下的所有 URL(文章頁、列表頁、首頁),數秒內全球生效。這是讓 HTML 可以「長快取 + 即時更新」的關鍵。

  3. URL 清除 / 全站清除:緊急用;全站清除會讓命中率瞬間歸零、origin 承受尖峰,慎用。

  1. 匿名與登入要分流:有 session cookie 的請求 BYPASS(或由邊緣程式改寫 cache key),匿名使用者共享同一份 HTML。個人化區塊(購物車數量、使用者名稱)改由前端在載入後用 API 補上,或用邊緣運算注入。

  2. origin 必須正確設定 s-maxage 或 CDN-Cache-Control:忘記設會落入 CDN 的預設值(有的預設不快取 HTML、有的預設快取 2 小時)。

  3. 有可用的清除機制:內容系統(CMS)發布時要能觸發 purge by tag,否則只能靠短 TTL。

判讀命中與命中率

L3.6cm L9.9cm 標頭 & 說明
& 這份回應在快取裡待了 537 秒。有 Age 幾乎就表示命中(0 或沒有則是剛回源)。
(RFC 9211) & 標準化格式:,可串多層(shield、edge 各一段)。Netlify、Fastly 等支援。
& Cloudflare:HIT、MISS、EXPIRED、STALE(SWR 供應舊版)、REVALIDATED、BYPASS(因 cookie / no-store)、DYNAMIC(依規則不快取此類型)。
& CloudFront: / / ;Fastly: / (加 看是哪個 PoP)。
& Vercel:HIT、MISS、STALE、PRERENDER、REVALIDATED。
& 部分 CDN 會輸出 、 等,可用 Resource Timing 的 在前端讀取()。

# -s 安靜、-o 丟掉 body、-D - 印標頭;連打兩次比較第二次是否命中
curl -s -o /dev/null -D - https://www.example.com/ | grep -iE "^(age|cache-control|cdn-cache-control|cache-status|cf-cache-status|x-cache|server-timing|vary):"

# 量測各階段時間(DNS、連線、TLS、TTFB、總計)
curl -s -o /dev/null -w "dns %{time_namelookup}s  tcp %{time_connect}s  tls %{time_appconnect}s  ttfb %{time_starttransfer}s  total %{time_total}s  proto %{http_version}\n" https://www.example.com/

# 假裝從另一個地區:多數 CDN 可用特定標頭或 --resolve 指到特定 PoP 的 IP
curl -s -o /dev/null -D - --resolve www.example.com:443:203.0.113.10 https://www.example.com/
const hits = { HIT: 0, MISS: 0, other: 0 };
for (const e of performance.getEntriesByType('resource')) {
  const st = e.serverTiming?.find((s) => s.name === 'cdn-cache');
  if (!st) continue;
  const status = st.description?.toUpperCase();
  hits[status in hits ? status : 'other']++;
}
console.table(hits);

// 也看看走的是什麼協定:h3 表示 QUIC
console.table(
  performance.getEntriesByType('resource').map((e) => ({ host: new URL(e.name).host, proto: e.nextHopProtocol }))
);

命中次數 ÷ 總請求次數。靜態資源目標 \(>\) 95%;HTML 依網站性質而異,內容型網站可達 80–90%,電商匿名頁面 60–80%。CDN 主控台都有此數據,也可依「位元組」計算(bandwidth savings)。命中率低的常見原因:cache key 太細(query string、cookie)、TTL 太短、長尾 URL 太多、沒有 origin shield、Vary 太多。

進階功能:影像、Early Hints、邊緣運算

影像 CDN

LCP 元素多半是圖片,而圖片是頁面位元組的大宗。影像 CDN 在邊緣即時做:依 標頭轉成 AVIF / WebP()、依 URL 參數縮放到需要的寬度、調整品質、去除 metadata,結果再快取。

<img
  src="https://img.example.com/cdn-cgi/image/width=800,quality=75,format=auto/hero.jpg"
  srcset="
    https://img.example.com/cdn-cgi/image/width=480,quality=75,format=auto/hero.jpg 480w,
    https://img.example.com/cdn-cgi/image/width=800,quality=75,format=auto/hero.jpg 800w,
    https://img.example.com/cdn-cgi/image/width=1200,quality=75,format=auto/hero.jpg 1200w"
  sizes="(max-width: 600px) 100vw, 800px"
  width="800" height="450" alt="..." fetchpriority="high">

Next.js 的 、Astro 的 、Nuxt Image 等元件就是在幫你產生這類 URL;自架時可用 imgproxy、Thumbor 搭配 CDN。

103 Early Hints

介紹的 Early Hints 特別適合由 CDN 發出:CDN 記住某個 URL 上次回應的 標頭,下次一收到請求就先回 103,讓瀏覽器在 origin 還在產生 HTML 的幾百毫秒內開始下載 CSS 與 LCP 圖片。Cloudflare、Fastly、Shopify 等都已支援。

邊緣運算(edge compute)

Cloudflare Workers、Fastly Compute、CloudFront Functions / Lambda@Edge、Vercel Edge Functions、Netlify Edge Functions 讓你在 PoP 上執行程式,冷啟動以毫秒計。常見的效能用途:

  • 依 cookie 改寫 cache key 或決定 BYPASS:匿名使用者共用快取,登入者回源。

  • 在快取的 HTML 上注入個人化片段:類似舊時代的 ESI,讓整頁仍可快取。

  • A/B 測試分派:在邊緣改寫 URL 或標頭,避免前端閃爍(CLS)與額外請求。

  • 邊緣 SSR:把的伺服器端渲染搬到離使用者最近的地方,資料 API 仍在遠端時要注意「邊緣到資料庫」的往返反而變多。

  • 標頭治理:統一補上 Cache-Control、安全標頭、,不必改 origin。

export default {
  async fetch(request, env, ctx) {
    const cookie = request.headers.get('Cookie') || '';
    const loggedIn = /session=/.test(cookie);
    if (loggedIn || request.method !== 'GET') {
      return fetch(request);                  // 登入者:直接回源,不進快取
    }
    const cache = caches.default;
    const cacheKey = new Request(new URL(request.url).toString(), request); // 去掉 cookie 影響
    let response = await cache.match(cacheKey);
    if (!response) {
      response = await fetch(request, { cf: { cacheTtl: 600, cacheEverything: true } });
      response = new Response(response.body, response);
      response.headers.set('Cache-Control', 'public, max-age=0, s-maxage=600');
      ctx.waitUntil(cache.put(cacheKey, response.clone()));
    }
    return response;
  },
};

常見錯誤

L4.6cm L8.9cm 錯誤 & 後果與修法
用 做版本化 & 部分 CDN 預設忽略 query string → 使用者拿到舊版;或每個參數組合各存一份 → 命中率低。改用雜湊檔名。
所有回應都帶 & CDN 拒絕快取。靜態資源與匿名 HTML 不要設 cookie;把分析用 cookie 改由 JS 設定。
& 快取碎成數千份。改用 client hints 或在邊緣把 UA 正規化成 mobile / desktop 兩類。
把 API 放在另一個網域 & 多一次 DNS + TCP + TLS,且 CORS 預檢再多一個 RTT。盡量同網域(用路徑區分)或至少 。
字型用第三方託管 & 因快取分區沒有共享優勢,還多一條連線。自行託管 並給一年快取。
沒開 HTTP/3 / Brotli & 邊緣通常一鍵開啟;確認 是 、 是 或 。
沒有 origin shield & 每個 PoP 各自回源,命中率低、origin 尖峰大。
TTL 太短又沒有 SWR & 每次過期都有使用者要等回源。加上 。

CDN 與 Web Vitals 的對應

L2.2cm L11.3cm 指標 & CDN 的影響
TTFB & 最直接:邊緣命中的 TTFB 通常 \(<\) 100 ms;未命中時也因暖連線與路徑優化改善。CrUX 中 TTFB 差、且使用者分布在多國的網站,CDN 是首選解法。
FCP / LCP & HTML 更早到 → 一切更早開始;影像 CDN 縮小 LCP 圖片位元組;Early Hints 提前發現 CSS 與圖片;HTTP/3 減少弱網下的隊頭阻塞。
INP & 間接:資源更快到達可減少載入期間的競爭;但 INP 主要取決於主執行緒工作,CDN 幫不了長任務。
CLS & 間接:字型與圖片更快到達,減少延遲載入造成的跳動;邊緣 A/B 分派避免前端切換造成的閃爍。

課堂實作

對三個知名網站執行程式碼 [lst:cdncurl] 的第一條指令兩次。從標頭判斷:(1)是哪家 CDN?(2)HTML 是否被邊緣快取(看 Age、狀態標頭)?(3)它的 Cache-Control 策略是什麼?再用第二條指令比較 TTFB,並在 DevTools Network 看 Protocol 欄是否為 h3。

用 WebPageTest(或任何多地點測試工具)分別從台灣、美國、歐洲對同一個網站測試,比較 TTFB 與 LCP。有 CDN 的網站各地差距應該很小;找出一個差距很大的網站,推測原因(沒有 CDN?HTML 不快取?API 在單一區域?)。

為一個假想的電商網站列出:首頁、分類頁、商品頁、購物車、結帳、、、雜湊資源、商品圖片。為每個 URL 寫出 與 (若需要),標註要貼的 cache tag,並說明商品價格改變時要清除什麼。

  1. CDN 對「完全不能快取」的動態 API 回應有沒有幫助?為什麼?

  2. 這行對瀏覽器與 CDN 分別意味著什麼?

  3. 為什麼帶 的回應不該被 CDN 快取?如果網站的框架對所有頁面都設 session cookie,該怎麼處理?

  4. 一個網站命中率只有 40%,可能原因有哪些?請依「最可能」排序。

  5. Early Hints 由 CDN 發出,跟由 origin 發出,各有什麼優缺點?

  6. 「邊緣 SSR」一定比「單一區域 SSR」快嗎?什麼情況下會更慢?

  • RTT 由距離決定,連線建立又要花好幾個 RTT;CDN 把交握對象換成 10 ms 外的邊緣節點,對所有回應(含動態)都有效,命中時更能完全免回源。

  • 架構關鍵字:PoP、Anycast、origin shield、cache key。cache key 越細命中率越低。

  • 用 或 分開控制 CDN 與瀏覽器;HTML 上 CDN 要配合 SWR、匿名 / 登入分流與 purge by tag。

  • 從 、、、 判讀命中;用 curl 與 Resource Timing 驗證,目標靜態資源命中率 \(>\) 95%。

  • 影像 CDN、Early Hints、邊緣運算分別針對 LCP 位元組、資源發現時間與個人化快取,是 CDN 之上最划算的三項功能。