LCP 優化實戰:四段時間模型幫你找到瓶頸,不再亂猜改哪裡
陳柏翰
網站效能工程師
LCP 分數跑不過,多半不在圖片大小
拿 Lighthouse 跑出 LCP 3.8 秒,直覺反應是「圖片太重了」。把 JPEG 換成 WebP、壓縮到 80KB,重跑,3.6 秒。改善 0.2 秒。瓶頸還在。(詳見 圖片格式選擇與 fetchpriority 設定)
在確認 LCP 元素之後,加上 fetchpriority="high" 屬性是最直接的 LCP 加速方式,可縮短瀏覽器 Tight Mode 中圖片的等待時間。
這個問題我見過太多次。LCP 的瓶頸不一定是圖片本身的大小,有時候是圖片根本還沒開始下載,有時候是伺服器 HTML 回應就已經慢了 2 秒。把時間花在壓縮圖片,等於在錯誤的地方用力。
web.dev 提供了一個四段時間模型,把 LCP 從使用者發出請求到最大元素渲染完成,拆分成四個可診斷的子段。搞清楚是哪一段在吃時間,再對症下藥,才能有效把 LCP 壓下來。如果你想先了解 LCP 在 Core Web Vitals 整體架構中的位置,可以參考Core Web Vitals 三大指標概覽。
如果想透過 JavaScript 程式讀取 LCP 各子階段的耗時數值,LargestContentfulPaint API 工程指南說明了 PerformanceObserver 的使用方式、renderTime 為 0 的診斷,以及 CI/CD 整合方法。
四個時間段,決定 LCP 的根因在哪
根據 LCP 四段時間優化官方指引,LCP 時間由以下四段組成:
- TTFB(Time to First Byte):從使用者發出請求到收到第一個位元組的時間。這是伺服器層的指標。
- Resource Load Delay:從 TTFB 完成到 LCP 資源(圖片)開始下載的時間差。這段的延遲通常來自瀏覽器的 preload scanner 還沒發現這個資源。
- Resource Load Duration:圖片實際下載花費的時間。這才是「圖片太重」影響的部分。
- Element Render Delay:圖片下載完到實際渲染到畫面的延遲。通常由 render-blocking JavaScript 或主執行緒阻塞造成。
web.dev 建議的最佳分布:TTFB 占總 LCP 時間的 40% 以下,Resource Load Duration 也在 40% 以下,兩段 Delay 合計 10% 以下。如果你的瀑布圖裡 Resource Load Delay 獨自占了 60%,壓縮圖片一點用都沒有。。關於消除渲染阻塞資源的完整修復方法,可以參考消除渲染阻塞資源。
用 DevTools 找到你的瓶頸在哪一段
Chrome DevTools 的 Performance 面板可以直接看 LCP breakdown。錄製頁面載入後,點選 LCP 事件,右側 Details 欄位會顯示四個子段的時間。PageSpeed Insights 也有「Largest Contentful Paint element」的詳細子段分析,在「Diagnose performance issues」區塊往下捲就找得到。 關於行動裝置 LCP的特殊考量,可以參考行動裝置 LCP 優化指南。
看到數字之後,先確認哪一段佔比最高,然後再進入對應的優化策略。不要跳過這步直接開始改東改西。
TTFB 高了,前端再怎麼改也有限
TTFB 是 LCP 的地基。如果伺服器回應 HTML 需要 2 秒,LCP 就從 2 秒起跳,再怎麼優化前端資源也只是在追趕。理想的 TTFB 是 200ms 以下,可接受範圍是 800ms 以下。
TTFB 高的根本原因通常是兩類:伺服器端的計算太慢(資料庫查詢、PHP 渲染、伺服器距離遠),或者 HTML 沒有被快取。
最直接的改法是把整頁 HTML 快取在 CDN edge。對 WordPress 網站來說,WP Rocket 或 LiteSpeed Cache 的「Full Page Cache」功能就在做這件事。快取命中的情況下,TTFB 可以從 8關於 CDN 如何系統性地改善 LCP 的四個子階段,包括 Cache Rules 設定與圖片 CDN 格式壓縮,可以參考 CDN 改善 LCP 的機制與實測的完整實測紀錄。00ms 降到 50ms 以內。但要注意,有登入狀態的頁面(購物車、會員頁)不適合這樣快取。
台灣流量主要來源如果是本地,Cloudflare 的免費方案就夠用;如果是東南亞混合流量,BunnyCDN 的亞太節點覆蓋比 Cloudflare 更密。但說老實話,TTFB 的瓶頸如果是 hosting 本身太慢,換 CDN 只能補救一部分,根本還是要換主機。更詳細的 TTFB 六個計時階段診斷、Server Timing API 工具和後端快取優化方法,見 TTFB 優化完整指南。
Resource Load Delay:圖片在等瀏覽器發現它
Resource Load Delay 是台灣前端最常忽略的 LCP 瓶頸,也是投入報酬率最高的優化點。這段時間代表瀏覽器從收到 HTML 到決定開始下載 LCP 圖片之間的等待時間,通常不是因為網路慢,而是因為圖片沒有被及早發現。
preload 讓瀏覽器提早發現圖片
瀏覽器有一個叫 preload scanner 的機制,在 HTML 解析的早期就掃描 `` 標籤,提早發起資源請求。一般的 `` 標籤如果放在 HTML 靠前的位置,通常不需要額外的 preload。
需要 preload 的情境:LCP 元素是 CSS background-image(瀏覽器要執行完 CSS 才知道要下載),或者圖片是由 JavaScript 動態插入的。這兩種情況瀏覽器的 preload scanner 掃不到,加上 preload 可以讓下載提早 300-500ms 開始。
<link rel="preload" as="image" href="/hero-image.avif" fetchpriority="high">
fetchpriority="high" 讓圖片在競爭中贏
preload 解決的是「發現時機」,fetchpriority 解決的是「優先順序競爭」。網頁載入時有很多資源同時在搶有限的頻寬,包括 CSS、JavaScript、字體、其他圖片。預設情況下,瀏覽器自己決定哪個先下。加上 fetchpriority="high" 就等於告訴瀏覽器這個圖片要插隊。。如果 LCP 候選元素是文字,font-display 的設定方式會直接影響文字出現的時間點。
Google 官方的測試數據顯示,對 LCP 圖片加上 fetchpriority="high",LCP 從 2.6 秒降到 1.9 秒,只加這一行屬性。這背後的機制是 Priority Hints API(W3C Level 1 規格),Chrome 102 以上全支援,Safari 17.2+ 也已支援。
最佳組合是兩者一起用:
<!-- HTML 中的 img 標籤 -->
<img src="/hero.avif" alt="..." fetchpriority="high" loading="eager">
<!-- 如果是 CSS background-image,需要同時加 preload -->
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high">
一個重要限制:整頁只能有一個 fetchpriority="high" 的元素。如果多張圖片都加了這個屬性,等於都不加,互相抵消。
LCP 圖片不要加 lazy loading
我看過不少專案把 `loading="lazy"` 套用在頁面所有圖片上,包括首屏的 hero image。這是一個會直接讓 LCP 爆掉的操作。
lazy loading 的機制是延遲到圖片接近視窗範圍才開始下載。對首屏的 LCP 元素來說,這表示瀏覽器在頁面載入初期不會去下載它,要等到後續某個判斷時機才觸發。根據 Largest Contentful Paint 完整規格,lazy loading 會直接拉長 Resource Load Delay 段。用 WebPageTest 在 3G 網路(Moto G4, 5Mbps)環境下跑的測試,LCP 圖片誤加 loading="lazy",平均使 Resource Load Delay 增加 1.4 秒(範圍約 0.8-2.1 秒),在 TTFB 已經優化的站點,這個錯誤往往成為 LCP 的主要瓶頸。
正確做法:
- 首屏 LCP 圖片:用
loading="eager"或完全不加 loading 屬性(預設行為就是 eager) - 折疊線以下的圖片:才用
loading="lazy"
如何判斷哪張是 LCP 元素?在 Chrome DevTools Performance 面板錄製載入後,直接標示 LCP element,或者在 Lighthouse 報告的 Diagnostics 區塊找「Largest Contentful Paint element」。確認之後,確保那張圖的 loading 屬性不是 lazy。
Resource Load Duration:圖片本身確實太重
前面三段都診斷完了,Resource Load Duration 還是偏高,這才是真正的「圖片太重」問題。這段的優化主要是格式和尺寸。
格式選擇上,AVIF 的壓縮率比 WebP 好 30-50%(同品質下),WebP 比 JPEG 好 25-35%。支援度方面,AVIF 在 Chrome 85+、Firefox 93+、Safari 16.4+ 都已支援。用 `
<picture>
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" alt="..." fetchpriority="high" loading="eager">
</picture>
尺寸控制用 srcset + sizes,讓瀏覽器依視窗寬度下載對應大小的圖片,避免手機用戶下載桌面版 2000px 寬的圖片:
<img
srcset="/hero-400.avif 400w, /hero-800.avif 800w, /hero-1200.avif 1200w"
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
src="/hero-1200.avif"
fetchpriority="high"
loading="eager"
alt="..."
>
一個常見的誤解:圖片格式優化只影響 Resource Load Duration 這一段。如果你的問題在 TTFB 或 Resource Load Delay,換成 AVIF 也只能省那一段的時間,整體 LCP 改善有限。
SPA 和 Next.js 的 LCP 特殊場景
純靜態 HTML 頁面用上面的方法就夠了。但如果你的站是 React SPA 或 Next.js,LCP 有個特殊問題。
CSR(Client-Side Rendering)的頁面,LCP 內容要等 JavaScript 執行完才能渲染。瀏覽器拿到 HTML 可能只有一個空的 `
Next.js 的 SSR/SSG 解決這個問題:HTML 在伺服器或 build time 就渲染好,瀏覽器拿到 HTML 直接看到內容,LCP 計時可以從 HTML 解析完就開始。
Next.js 的 `
import Image from 'next/image'
export default function Hero() {
return (
<Image
src="/hero.avif"
alt="..."
width={1200}
height={630}
priority // fetchpriority="high" + preload
/>
)
}
值得注意的是,Next.js 16 已把 priority prop 標記為 deprecated,建議改用 loading="eager" 或直接寫 fetchPriority="high"。行為上是等效的,但 API 更貼近瀏覽器原生屬性。根據 Pagepro 的測試,正確設定 hero image priority 可以讓 LCP 改善 300-800ms,但如果多個圖片都設定 priority,反而會增加 400-1200ms 的延遲。一頁就加一個。
修復 LCP 的順序
改 LCP 最容易犯的錯誤是同時動太多地方,然後不知道哪個有效。我的習慣是這樣排優先順序:
- 先量:用 PageSpeed Insights 或 Chrome DevTools 看 LCP breakdown,找出哪一段最長。不確定你的頁面 LCP element 是哪個?先參考 LCP 元素識別的完整診斷流程。
- TTFB 超過 800ms?先處理 hosting / CDN / HTML caching,其他都是徒勞
- Resource Load Delay 超過 200ms?加 preload + fetchpriority="high",確認 LCP 圖片沒有 lazy loading
- Resource Load Duration 超過 500ms?壓縮圖片、換 AVIF/WebP 格式、用 srcset 給對應尺寸
- Element Render Delay 超過 200ms?排查 render-blocking JavaScript,考慮 defer/async 或 code splitting
按這個順序,通常第一輪調整就能看到明顯改善。每次調整後,重新跑一次 PageSpeed Insights 或 DevTools Performance,確認那一段的時間占比確實下降了。如果 Resource Load Delay 從 300ms 降到 50ms 但 LCP 總時間沒有等比例改善,代表瓶頸已轉移到另一段,重新量一次再決定下一步。如果你碰到 Lighthouse 數字過了但 CrUX(現場數據)還是差,請看下方 FAQ 的第 8 題。
根據 Google Search Console Core Web Vitals 報告的說明,CrUX 數據以 28 天滾動窗口更新,改動後要等 4 週左右才能在 Search Console 看到完整反映。
主執行緒的長任務不只影響 LCP,對 INP 的影響更直接。INP 優化的診斷與修復方法說明了如何用 Chrome DevTools 找出阻塞互動的長任務。
LCP 2.5 秒是對所有設備都一樣的標準嗎?
是的,2.5 秒是 Google 對 LCP「Good」的門檻,不分設備型別。但實際上手機的 LCP 普遍比桌面差,因為手機 CPU 較慢(影響 Element Render Delay)、網路不穩定(影響 Resource Load Duration),所以在測試時要特別關注行動版的 CrUX 數據,不能只看桌面 Lighthouse。
Lighthouse 測 LCP 1.8s,但 PageSpeed 的 CrUX 顯示 3.2s,哪個才準?
CrUX 是真實使用者的現場數據(Field Data),Lighthouse 是在受控環境下模擬的實驗數據(Lab Data)。兩者本來就不該完全一致。CrUX 更能反映你的使用者實際體驗,所以在 Google 排名計算中使用的是 CrUX,不是 Lighthouse 分數。Lighthouse 適合本機調試,CrUX 才是最終指標。落差大的原因通常是:使用者設備多元(低端 Android 手機)、第三方 JavaScript 在 lab 環境下還沒觸發、或者地理位置的網路延遲差異。
換成 WebP 格式後,LCP 幾乎沒改善,是哪裡出了問題?
格式換成 WebP 只能改善 Resource Load Duration 這一段。如果你的瓶頸是 TTFB 或 Resource Load Delay,換格式效果就很有限。建議先用 DevTools 看 LCP breakdown,確認各段時間占比,再決定下一個優化目標。
fetchpriority="high" 需要同時加 preload 嗎?
兩個解決的問題不同。preload 幫助瀏覽器提早「發現」資源,fetchpriority 幫助資源在競爭中「優先」下載。對 `` 標籤放在 HTML 前段的情況,preload scanner 通常能提早發現,只需要加 fetchpriority="high" 就夠。如果是 CSS background-image 或 JS 動態插入的圖片,才需要同時加 preload。兩者搭配使用當然沒問題,但 preload 本身有副作用(多一個請求),如果瀏覽器本來就能提早發現,多加反而可能增加延遲。
LCP 元素是 CSS background-image,要怎麼優化?
CSS background-image 是 LCP 優化最麻煩的情境。瀏覽器的 preload scanner 看不到 CSS 裡的背景圖,必須等 CSS 解析完才知道要下載。解法是加 ``。如果可以的話,考慮把背景圖改成 `` 標籤搭配 CSS 定位,這樣 preload scanner 可以提早發現,也更容易設定 fetchpriority。
用了 CDN,TTFB 沒有明顯改善,可能是什麼原因?
最常見的原因是 CDN 沒有快取 HTML,只快取了靜態資源(CSS、JS、圖片)。HTML 本身是動態生成的,每次都 bypass CDN,繞回到 origin server。確認 CDN 設定有開啟「Cache HTML」或「Full Page Cache」,並且 Cache-Control header 允許 CDN 快取。另一個原因是 CDN 的節點距離你的主要使用者群太遠。,設定正確的靜態資源快取策略能直接縮短 LCP
Next.js 的 Image 元件加了 priority,還需要再做其他 LCP 優化嗎?
priority prop 主要處理的是 Resource Load Delay(fetchpriority + preload)。TTFB 高的問題還是要從 hosting 和 CDN 下手,圖片格式過大要另外處理 srcset 和 AVIF。另外,Next.js 在 App Router 下使用 Server Components 可以進一步縮短 TTFB,如果還在用 Pages Router 搭配大量 getServerSideProps,這部分值得評估遷移成本。
CrUX 資料顯示 LCP 不好,但 Lighthouse 測都過,怎麼回事?
主要原因有幾個:一是真實使用者的設備和網路比 Lighthouse 模擬的更差,二是某些第三方 JavaScript(廣告、聊天工具、分析)在 Lighthouse 環境下沒有完全觸發,實際環境中會阻塞主執行緒影響 LCP,三是 CrUX 數據包含過去 28 天的舊資料,即便你今天改好了,CrUX 也需要幾週才會反映。診斷方向:用 WebPageTest 的 Moto G4 + 3G 模擬跑一次,看看是否複現 CrUX 的問題。
怎麼知道 render-blocking JavaScript 是否影響了 LCP?
在 Chrome DevTools Performance 面板錄製頁面載入,找 LCP 事件的時間點,往前看 Main thread 的活動。如果在 LCP 發生前有大量的 JavaScript 執行(橙色長條)佔用主執行緒,那就是 Element Render Delay 段的根因。Lighthouse 報告的「Reduce JavaScript execution time」和「Eliminate render-blocking resources」診斷項目也會直接標出問題腳本。
深入了解渲染阻塞的根本原因,參考:Critical Rendering Path 與 render-blocking 原理。
LCP 修好之後,Google 多久才更新 CrUX 資料?
CrUX 使用 28 天滾動窗口。改動生效後,最快需要 4 週才能讓舊的差數據完全從窗口內滾出。在 Google Search Console 的 Core Web Vitals 報告中,你會先看到標記為「Improving」的趨勢,完整改善通常要 4-6 週才完全反映。
延伸閱讀:CLS 累積版面位移的計算方式與評分標準,完整掌握 Core Web Vitals 三大指標。