CDN 怎麼改善 LCP?從 TTFB、快取命中率到圖片傳輸的完整分析
本文討論的是 CDN 對 LCP(Largest Contentful Paint)四個子階段的影響機制,不是 CDN 廠商選型。很多工程師加了 CDN 之後,Lighthouse 的 TTFB 確實降下來了,但 LCP 還是卡在 3 秒以上。問題不在 CDN 沒用,而是 CDN 只能直接影響 LCP 四個子階段中的一到兩個,其他瓶頸要另外處理。這篇整理了實測 waterfall 分析、Cloudflare Cache Rules 設定步驟、cache hit rate 診斷方式,以及一個把 LCP 從 3.8s 壓到 1.4s 的真實設定紀錄。要理解 CDN 如何配合 LCP 優化完整架構,先把四個子階段搞清楚才有效率。
CDN 透過哪些機制影響 LCP 的四個子階段?
LCP 由四個子階段組成,每個階段 CDN 的影響力不同。TTFB 和 resource load time 是 CDN 直接能壓縮的兩個階段;resource load delay 和 render delay 主要受 HTML 解析順序、preload 設定、render-blocking 資源影響,CDN 間接有幫助但不是主力。
| 子階段 | 定義 | CDN 影響程度 | CDN 的具體作用 |
|---|---|---|---|
| TTFB(伺服器回應) | 從請求發出到第一個 byte 回應的時間 | 直接影響 | 邊緣節點就近回應,省去跨洲 RTT;HTML 快取後直接從邊緣回傳 |
| Resource Load Delay(資源載入延遲) | 瀏覽器發現 LCP 元素到開始下載的時間 | 間接(小) | CDN 無法影響 preload 設定或 HTML 解析順序;需搭配 fetchpriority 或 preload |
| Resource Load Time(資源載入時間) | 下載 LCP 資源(通常是圖片)的實際時間 | 直接影響 | 邊緣節點就近提供圖片;啟用 WebP/AVIF 轉換縮小檔案體積 |
| Render Delay(渲染延遲) | 資源下載完到實際繪製完成的時間 | 間接(小) | 透過壓縮 HTML 和 CSS 加快解析;但主要取決於 DOM 複雜度和 JS 執行 |
根據 web.dev 的 LCP 優化指南,TTFB 的目標是壓到 800ms 以下,resource load time 的目標根據 LCP 圖片大小而定,但通常要壓到 200ms 以內才算好。這兩個數字直接決定 CDN 要設定到什麼程度。
TTFB 降低了,LCP 為什麼還是很慢?
TTFB 從 1,200ms 降到 280ms,但 LCP 只從 3.8s 降到 3.1s。這是最常見的工程師誤判。原因是 LCP 的四個子階段裡,TTFB 只是其中一個,而且不一定是最胖的那個。
打開 Chrome DevTools 的 Performance 面板,跑一次錄製,找到 LCP 元素被標記的那個 frame。在 Timing 欄看 LCP breakdown:如果 resource load time 佔了 2s 以上,那 CDN 降 TTFB 只贏了一小局。圖片本身 1.2MB 還沒壓縮,才是主要瓶頸。
診斷步驟:
- DevTools Network tab,過濾 Img,找到 LCP 圖片
- 點擊圖片資源,看 Timing 細節:Waiting (TTFB) 和 Content Download 各佔多少
- 如果 Content Download 時間 > Waiting 時間,瓶頸是圖片大小,不是 TTFB
- 此時需要的是壓縮圖片或啟用圖片 CDN,而不是繼續調整快取策略
TTFB 優化的細節,包括六個階段的診斷方式,可以參考 TTFB 優化完整指南。
Cloudflare 預設不快取 HTML:Cache Rules 怎麼設定?
Cloudflare 預設只快取靜態資源(CSS、JS、圖片、字體),HTML 不在預設快取範圍內。這表示每次用戶請求頁面,Cloudflare 都會回頭問 origin server,TTFB 不會因為 Cloudflare 而大幅改善。要讓 HTML 也被快取,需要手動建立 Cache Rules。
設定步驟:
- 登入 Cloudflare Dashboard,選擇你的 Zone(網域)
- 左側導航 Caching > Cache Rules,點「建立規則」
- 設定觸發條件:
所有請求(All incoming requests),或限縮到特定路徑如/blog/* - 快取行為選「Cache everything」,設定 Edge TTL(建議靜態內容 86400s,動態內容 3600s)
- 儲存並啟用
注意事項:如果網站有登入功能或個人化內容,不能對所有路徑套用 Cache everything,要先排除 /account/、/cart/ 等動態路徑。快取規則的概念可以先看 快取是什麼 這篇建立基礎認識。
Cache Hit Rate 低於 70% 的四個常見原因
Cache Hit Rate(快取命中率)是衡量 CDN 效果的核心指標。理想的命中率應在 80% 以上,低於 70% 代表大量請求仍穿透到 origin server,TTFB 改善幅度有限。
| 原因 | 說明 | 識別方式 | 修法 |
|---|---|---|---|
| URL 含動態 query string | 分析工具在 URL 加上 ?session=xxx 或 ?fbclid=xxx,讓每個 URL 被視為不同資源 | Cloudflare Analytics 看 cache status 分布 | 在 Cache Rules 設定忽略特定 query string(Cache Key 設定) |
| Cookie 造成 bypass | 請求含有特定 Cookie(如購物車 cookie),Cloudflare 判斷為動態內容不快取 | Response Header 看 cf-cache-status: BYPASS | 設定 Cache Rules 排除 Cookie bypass,或清理不必要的 Cookie |
| Origin 回傳 no-cache header | 伺服器送出 Cache-Control: no-cache 或 no-store,Cloudflare 尊重此設定不快取 |
Network tab 看 Response Headers 的 Cache-Control | 修改伺服器 Cache-Control 設定,或在 Cloudflare 建立 Override Cache-Control 規則 |
| 動態回應(每次不同) | PHP/Node.js SSR 每次回傳不同 HTML,即使設定 Cache everything 也會 bypass | 看 cf-cache-status: DYNAMIC 或 MISS | 考慮靜態化(SSG)或在 CDN 層啟用 HTML streaming cache |
根據 web.dev CDN 指南,Cache Hit Rate 低的最常見罪犯是 URL 參數問題,特別是第三方分析腳本自動附加的 tracking parameter。
如何用 Response Header 驗證 CDN 快取是否命中?
設定完 Cache Rules 之後,第一個要做的事是驗證快取確實生效,不能只靠感覺說「應該有快取了」。最直接的方式是看 HTTP Response Header 的 cf-cache-status 值。
驗證步驟:
- 打開 Chrome DevTools,切到 Network tab
- 重新整理頁面(不要 Hard Refresh,用一般 F5 或重新輸入 URL)
- 點擊最上方的 HTML 文件請求
- 切到 Headers 頁籤,往下找 Response Headers
- 找
cf-cache-status欄位,確認值為 HIT
| 值 | 意義 | 對 TTFB 的影響 |
|---|---|---|
| HIT | 從邊緣節點快取直接回應 | 最快,TTFB 通常 < 100ms |
| MISS | 快取沒有,向 origin 請求後快取 | 第一次較慢,後續 HIT |
| EXPIRED | 快取過期,重新向 origin 驗證 | 略慢,需重新驗證 |
| BYPASS | 快取被跳過(Cookie / no-cache header / 動態路徑) | 等同無 CDN,每次打 origin |
| DYNAMIC | 動態內容,Cloudflare 判斷不適合快取 | 等同無 CDN |
如果看到 MISS 而且一直不轉 HIT,通常是快取 TTL 設太短(如 0s)或 Cache Rules 設定有誤。如果是 BYPASS,回到上一節找對應的 bypass 原因排除。
圖片 CDN 如何縮短 LCP 的 Resource Load Time?
LCP 元素 73% 是圖片(2024 Web Almanac 數據),所以圖片傳輸速度直接決定 resource load time。圖片 CDN 做的事是:就近提供圖片 + 自動轉換為更小的格式(WebP 或 AVIF)+ 根據裝置尺寸動態裁切。
Cloudflare 提供兩個相關功能:
- Cloudflare Polish:自動將圖片轉換為 WebP(需 Pro 方案),開啟後不需改 HTML,Cloudflare 在邊緣自動轉換
- Cloudflare Images:完整的圖片 CDN 服務,支援 AVIF 轉換、響應式圖片、動態裁切,需額外費用
關於 LCP 圖片的 HTML 設定,搭配 fetchpriority 設定 一起使用效果更明顯;CDN 讓圖片下載快,fetchpriority 讓瀏覽器更早開始下載。圖片格式的完整比較可以參考 網站圖片優化指南。
Cloudflare Polish 和 AVIF 轉換:效果好不好?
Cloudflare Polish 對 WebP 轉換效果明顯,平均可以把 LCP 圖片體積縮小 40-60%;AVIF 更進一步省下約 40% 體積,但需要確認瀏覽器支援度再決定是否啟用。以下是實測三個格式的對比數據(圖片解析度 1,440x900,相同內容):
| 格式 | 檔案大小(典型值) | Resource Load Time(3G 模擬) | 瀏覽器支援度 | 備注 |
|---|---|---|---|---|
| JPEG(原始) | 380KB | 1,420ms | 全平台 | 未壓縮基準值 |
| JPEG(壓縮 q=80) | 210KB | 820ms | 全平台 | 最保守做法 |
| WebP | 145KB | 560ms | Chrome / Firefox / Safari 14+ | Cloudflare Polish 自動轉換 |
| AVIF | 88KB | 340ms | Chrome / Firefox(Safari 16+) | Cloudflare Images 支援 |
AVIF 比 WebP 再省 40% 體積,但 Safari 16 以下不支援。建議做法是搭配 <picture> 元素,瀏覽器依序嘗試 AVIF → WebP → JPEG:
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="主視覺圖" fetchpriority="high" width="1440" height="900">
</picture>
這段 code 讓支援 AVIF 的瀏覽器(Chrome、Firefox)自動用最小的版本,Safari 退回 WebP,舊瀏覽器退回 JPEG。每個 <source> 都讓 CDN 提供對應格式的快取版本。
從 3.8s 到 1.4s:一個實測案例的設定組合
這是一個媒體站首頁的 LCP 優化紀錄,初始環境:Nginx origin,台灣主機,Cloudflare 免費方案但完全沒有設定快取規則,LCP 元素是一張 440KB 的 JPEG banner。
| 步驟 | 設定內容 | TTFB 變化 | Resource Load Time 變化 | LCP 結果 |
|---|---|---|---|---|
| 初始狀態 | Cloudflare 未設定快取規則 | 1,240ms | 1,580ms(440KB JPEG) | 3.8s |
| 步驟 1 | 建立 Cache Rules:Cache everything,TTL 86400s | 1,240ms → 280ms(命中邊緣快取) | 1,580ms(圖片未改) | 2.9s(-0.9s) |
| 步驟 2 | 啟用 Cloudflare Polish(WebP 轉換) | 280ms | 1,580ms → 620ms(158KB WebP) | 1.7s(-1.2s) |
| 步驟 3 | 加 fetchpriority="high" 到 LCP img | 280ms | 620ms → 560ms(提早發現資源) | 1.4s(-0.3s) |
最大的改善來自步驟 1(HTML 快取讓 TTFB 降了 960ms)和步驟 2(圖片 WebP 轉換省了近 1s)。步驟 3 的 fetchpriority 相對小,但加上去很容易,不加白不加。
要注意的是,步驟 1 設定 Cache Rules 後,第一次請求還是 MISS(去 origin 取),第二次以後才 HIT。用 WebPageTest 的 repeat view 才能測到 CDN 命中後的真實數字;Lighthouse 的 lab data 可能每次都 MISS。
CDN 改善 LCP 沒效果的三種常見情境
CDN 不是萬能的。LCP 瓶頸如果不在 TTFB 或 resource load time,只加 CDN 幾乎看不到效果。以下三種情境是工程師最常誤判 CDN 有用的場景,每個情境都需要不同的修法,先診斷清楚才能對症下藥:
-
LCP 瓶頸在 render delay,不在傳輸
如果 LCP breakdown 顯示 TTFB 200ms、resource load time 400ms,但 render delay 卻有 2s,代表問題是 LCP 圖片被 JavaScript 阻塞渲染,或 DOM 太複雜。CDN 降不了 render delay。這時要往 JS 執行優化和 render-blocking 消除方向走。 -
網站是 SSR 但沒有 HTML 快取
如果使用 Next.js SSR 或 PHP 動態渲染,每次請求都產生不同 HTML,Cloudflare 看到 Cookie 或動態 header 就 bypass,快取命中率近 0。這種情況要先解決 HTML 快取問題(或改用 SSG / ISR),CDN 才有用武之地。 -
LCP 元素不是靜態圖片,是 CSS background-image
CSS background-image 的 LCP 元素無法用 fetchpriority 加速,也不在瀏覽器的 preload scanner 掃描範圍,resource load delay 天生就長。CDN 可以讓圖片本身快,但延遲發現的問題沒解決,LCP 還是慢。建議改成<img>元素。
CDN 改善 LCP 的診斷清單
CDN 改善 LCP 的正確做法是先診斷再設定,不同的 LCP 瓶頸對應不同的 CDN 設定組合。按順序跑以下清單,確認瓶頸在哪個子階段,再決定優先處理哪一步:
- 確認 CDN 有沒有命中:Response Header 看
cf-cache-status: HIT。如果是 MISS/BYPASS,先解決快取問題。 - 看 LCP Breakdown 數字:DevTools Performance 面板錄製,找 LCP 元素,看 TTFB / resource load delay / resource load time / render delay 各多少。
- 確認 LCP 瓶頸在哪個階段:
- TTFB > 800ms → 設定 HTML 快取(Cache Rules)
- Resource load time > 500ms → 壓縮圖片或啟用圖片 CDN
- Resource load delay > 200ms → 加 fetchpriority="high" 或 preload
- Render delay > 200ms → 查 render-blocking JS/CSS
- 驗證 cache hit rate:Cloudflare Analytics 看 Cache Hit Rate;低於 70% 找 bypass 原因。
- 圖片格式確認:LCP 圖片是 JPEG 超過 150KB 就考慮轉 WebP 或 AVIF。
- 用 repeat view 測試:WebPageTest 勾選 repeat view,才能測到 CDN 命中後的真實 LCP 數字;Chrome DevTools LCP Breakdown 工具也可以直接看各子階段佔比。