---
# System prepended metadata

title: Largest Contentful Paint（LCP）
tags: [seo, web vitals]

---

# LCP (Largest Contentful Paint)

> 測量可視區域中最大圖片、文字區塊或影片的顯示時間 (從使用者首次進入相應頁面的時間開始計算)
![image](https://hackmd.io/_uploads/rkCdfmYHye.png)

## LCP Element
- 以下是系統會視為最大內容繪製的元素類型：
    - `<img>` 元素 (動畫內容會以第一個影格顯示時間為標準，如 GIF 或 PNG 動畫)
    - `<svg>` 元素內的 `<image>` 元素
    - `<video>` 元素 (使用海報圖片載入時間或影片第一個畫面顯示時間，以較早者為準)
    - 透過 `url()` 載入 background-image 的元素 (非 CSS 漸層)
    - 包含文字節點或其他內嵌文字元素子項的區塊元素
- 瀏覽器能透過經驗判斷內容是否真的出現，例如以下狀況就會被視為 `not-contentful`
    - 透明度為 0 的元素
    - 佔據整個 viewport 的元素，會被視為背景而非內容
    - placeholder image 或 loading 畫面

### FCP vs LCP
- 這個指標和 FCP (First Contentful Paint) 的 contentful 標準有些微不同
        - LCP 會使用『主要內容』繪製的時間（較為精確）
        - FCP 會使用任何內容繪製到螢幕上的時間
- 元素大小根據使用者可視範圍的大小為依據，超出畫面的部分或是元素被截斷的部分不會被計入
- LCP 會在什麼時間點被回報？
    - 於 First Paint 時，識別到最大內容元素時被回報
    - 後續重新偵測到其他被識別為最大內容的元素時，會再次回報
    - Ex. Hero 畫面，於初始化面單純顯示文字
        -> LCP 會回報像是 `h2` 或 `p` 的元素
        -> 當實際畫面出現時，則會接續使用 `img` 元素回報
    - 就算最大元素後面被移除，除非出現更大的元素，不然仍會以被移除前的最大元素繪製時間作為標準
    - 當使用者開始與頁面互動時，會停止回報（可視範圍改變了）

## Report Insights
- 應使用最後被 Report 的 PerformanceEntry 作為標準 (也就是最後被判定為 LCP Element 的回報時間)
- 可以搭配 FCP 和 TTFP 一起看
    - TTFP 為使用者進到頁面後 HTML 的第一個 byte 被載入的時間，時間花費越多代表 HTML 顯示的時間越慢，進而導致更難達到 LCP<2.5s 的標準
    - 高的 TTFP 可能由使用者離 server 較遠、server 出現多個 redirect、無法使用 cache 或是網路速度等因素造成
    - TTFP 越長、FCP 越慢，暗示著顯示畫面所需載入的資源較多，這個問題在 client side rendering 特別容易發生
- 在尋找對策時，可先關注兩個部分，『 Initial HTML Document』和『LCP Resource』載入的時間
    - 透過 DevTools 找到 LCP Element，並觀測這個資源在 Network Waterfall 中載入的時間
        ![image](https://hackmd.io/_uploads/rkr745IBJx.png)
    - 除了能盡快開始載入 LCP Resource 外，也必須盡可能在 Resource 載入後盡快 render LCP Element

## LCP Time Breakdown

![image](https://hackmd.io/_uploads/H1H_M5vryx.png)
- 每個頁面的 LCP Value 都可以分成以下幾個階段
    - Time to First Byte (TTFB) - 從使用者開始載入這個頁面，一直到接收到第一個 Byte 的 HTML 這段時間
    - Resource Load Delay - 為 TTFB 到載入 LCP Resouce 中間的時間差，如果 LCP Element 不需要額外載入 resource 來渲染，那這個時間為 0
    - Resource Load Duration - 載入 LCP Resource 本身所需要的時間，如果 LCP Element 不需要額外載入 resource 來渲染，那這個時間為 0
    - Element Render Delay - LCP Resource 載入完成到 LCP Element 完成渲染的這段時間
- 需要注意的一點是要盡可能優化上述所有階段，因為在某些情況下單單優化一個部分並無法提升 LCP，很可能只會將單一階段減少的時間移動到其他階段
    - e.g. 假設根據上面的 waterfall，只將 image 的 file size 壓縮或是轉成其他格式，將可以減少 Resource Load Duration，但減少的時間只會被移到 Element Render Delay 上，並無法提升 LCP
        ![image](https://hackmd.io/_uploads/Hy-EXRwHkg.png)
        - 原因在於實際 render LCP element 的時間是在所有 JavaScript 被載入之後，才一次性顯示，因此實際顯示到畫面上的時間並沒有改變
- 要優化不同區段，須先了解理想中各區段所佔的比例
    | LCP sub-part           | % of LCP |
    | ---------------------- | -------- |
    | Time to first byte     | ~40%     |
    | Resource load delay    | <10%     |
    | Resource load duration | ~40%     |
    | Element render delay   | <10%     |
    | TOTAL                  | 100%     |
    - 兩個包含 delay 的部分，應盡可能將數字接近 0
    - 另外兩個會受到網速影響，因此本來就需要一定的時間
- 總歸來說總時長只要在 2.5s 以下就好，上面的時間分配並沒有那麼重要，但如果在兩個 delay 部分沒有處理好，那將分數維持在 2.5s 下就會非常困難。因此：
    - 應將大部分的 LCP 時間用於載入 HTML 和 LCP 資源
    - 在 LCP 之前，若 HTML 文件或 LCP 資源尚未開始載入，都是可以改善的機會
- 小提醒 - 不要試圖將這些百分比用確切的數值表示，因為這些時間區間只有在與彼此相對應的情況下才有意義

## LCP Optimizations

### 降低 Resource Load Delay
> Goal：確保 LCP Resource 能儘早開始載入
- 雖然理論上 Resource 最快能開始載入的時間是在 TTFB 之後，但實際上瀏覽器開始載入這些資源前還是會有一些 Delay。因此理想狀態下，LCP Resource 應該和 First Resource 同時載入，也就是說如果 LCP Resource 載入的時間比 First Resource 載入的時間還要慢，那就代表還有進步空間
    ![image](https://hackmd.io/_uploads/rk7Avydrkx.png)
- 總結出兩個主要影響 LCP Resource 載入速度的因素
    - Resource 何時被發現並開始載入
    - Resource 載入的優先程度
- 優化 Resource 被發現的時間點
    :::warning
    需要注意的是，如果 LCP 資源託管在不同的來源，即使可從 HTML 來源發現，也可能無法盡早開始載入，因為這些要求需要瀏覽器先連線至該來源，才能開始載入資源。盡可能將重要資源託管在與 HTML 文件資源相同的來源，這樣這些資源就能重複使用現有連線，節省時間
    :::
    - 為了確保 LCP Resource 儘早開始載入，就要讓這個 Resource 能在 initial HTML response 裡面被瀏覽器的 [preload scanner](https://web.dev/articles/preload-scanner) 找到
    - 以下為 LCP Resource 能夠在 HTML 被發現的情況
        - LCP Element 為 `<img>`，且他的 `src` 或 `srcset` 能在 initial HTML 中找到
        - LCP Element 雖然是使用 CSS background image，但有在 HTML 中透過 `<link rel="preload">` 或 [`Link` Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) 進行 preload
    - 以下為 LCP Resource 在 HTML 無法被發現的情況，瀏覽器需要跑 script 或是引入 stylesheet 才能發現 LCP Resource (通常會包含 Network Request)
        - LCP Element 為 `<img>`，且是透過 JavaScript 動態加到頁面上的
        - LCP Element 是透過 JavaScript Library lazyload 進來的，通常會透過 `data-src` 或 `data-srcset` 暫時隱藏 `src` 和 `srcset`
        - LCP Element 使用 background-image
    - 如果因為存在於 CSS 或 JavaScript 中導致沒辦法在 Inital HTML 中找到 LCP Resource，那就最好能以高的 [fetch priority](https://web.dev/articles/optimize-lcp#optimize_the_priority_the_resource_is_given) 被 preload
        ```html
        <!-- Load the stylesheet that will reference the LCP image. -->
        <link rel="stylesheet" href="/path/to/styles.css">

        <!-- Preload the LCP image with a high fetchpriority so it starts loading with the stylesheet. -->
        <link rel="preload" fetchpriority="high" as="image" href="/path/to/hero-image.webp" type="image/webp">
        ```
    :::danger
    在大多數網頁中，只要確保 LCP 資源開始載入的時間與第一個資源相同即可，但要注意，您可能會建立出無法及早偵測到任何資源的網頁，且所有資源的載入時間都會比 TTFB 晚許多。因此，雖然與第一個資源進行比較是找出改善機會的好方法，但在某些情況下可能不夠準確，因此仍建議您測量與 TTFB 相關的這段時間，並確保這段時間保持在較短的範圍內 
    :::
- 優化 Resource 載入的優先程度
    - 就算 LCP Resource 能從 HTML 上找到，還是有可能不是接下來最優先被載入的資源，有可能出現在瀏覽器判斷有其他更優先需要載入的資源
        - e.g. 如果在 LCP image 的 `<img>` 上面加上 `loading="lazy"`，將導致資源會在 layout 能夠確認 Element 在 viewport 內才會開始載入，就會造成比原本應該載入的時間晚一點
            :::danger
            請勿延後載入 LCP 圖片，否則會導致不必要的資源載入延遲，並對 LCP 造成負面影響
            :::
    - 因為圖片並非 blocking 的資源，即止沒有延遲載入瀏覽器也不會優先載入圖片，因此如果能判定頁面的 LCP Element 為何，可以透過 `fetchpriority` 來提示瀏覽器哪些資源最重要，為資源提高優先權
        ```html
        <img fetchpriority="high" src="/path/to/hero-image.webp">
        ```
        - 注意：如果對超過一個 `<img>` 上設置，將對加速 LCP 上沒有幫助！
    - 也可以透過降低其他已經出現在 HTML 上但不需要馬上顯示的圖片，如輪播圖中的其他圖片
        ```html
        <img fetchpriority="low" src="/path/to/carousel-slide-3.webp">
        ```
        - 這樣可以將頻寬分配給其他更優先的資源
        - 可以透過 DevTools 中觀察載入的優先順序，再來決定策略
- 在優化 LCP Resource 發現時間和資源載入時間後，waterfall 可能如下
    ![image](https://hackmd.io/_uploads/SyONVbOB1x.png)

### 降低 Element Render Delay
> Goal：確保載入 LCP Resource 載入後能馬上 Render 出來
- 主要會造成無法馬上 render 的原因是被其他可能因素 block 住
    - 由於 `<head>` 中的 stylesheet 或 synchronous scripts 仍在載入中，導致 block 住整個網頁的 render
    - LCP Element 還在等待某段 JavaScript 的執行才會被加到 DOM 上
    - 主執行緒因花費較長時間的工作而遭到堵塞，而 render 的工作需要等到這些工作完成才能結束
- 減少或內嵌會阻礙 render 的 stylesheet
    - 從 HTML 載入的 stylesheets 會阻擋後面所有要 render 的內容，畢竟你不會想要 render 沒有樣式的 HTML，但同時也可能因為 stylesheet 太大需要很多時間載入，導致 LCP Element 很晚才能 render
        ![image](https://hackmd.io/_uploads/B1WZtxtHyx.png)
    - 在 stylesheet 不大的情況下，可以使用 inline stylesheet 來避免多餘的 network request
    - 大多數情況會選擇盡可能縮小 stylesheet，至少要比 LCP Resource 還要小才不會擋到 render
        - 透過 Chrome DevTools 找到 [沒有用的 CSS](https://developer.chrome.com/docs/lighthouse/performance/unused-css-rules?hl=zh-tw) 並移除
        - 將 stlyesheets 拆成 chunks 並區別哪些在 initial pageload 需要、[哪些可以 lazy load 進來](https://web.dev/articles/defer-non-critical-css)
        - 透過 [minify 和 CSS 壓縮](https://web.dev/articles/reduce-network-payloads-using-text-compression) 來減少檔案大小
- 延後或內嵌會阻礙 render 的 JavaScript
    - 實際上幾乎不會需要在 `<head>` 中放入 synchronous 的 scripts (沒加上 `async` 或 `defer`)，如果加上通常都只會對效能產生負面影響
    - 如果 JavaScript 程式碼需要在網頁載入時盡早執行，建議將其內嵌，以免在等待其他網路要求時延遲 render。不過，與 stylesheet 一樣，應該只在程式碼很小時才內嵌
        ```html
        <!-- ❌ Don't -->
        <head>
          <script src="/path/to/main.js"></script>
        </head>

        <!-- ✅ Do -->
        <head>
          <script>
            // Inline script contents directly in the HTML.
            // IMPORTANT: only do this for very small scripts.
          </script>
        </head>
        ```
- 使用 [Server-Side Render (SSR)](https://web.dev/articles/rendering-on-the-web#server-side_rendering)，在 server-side 執行 client-side 邏輯並回傳完整的 HTML
    - 從 LCP 優化的角度而言，SSR 有兩個主要好處
        - image source 可以直接在 HTML 中找到，代表可以更早開始載入
        - 頁面不會需要多餘的 JavaScript requests 來完成畫面的 render
    - SSR 主要的壞處是他需要時間在 server-side 進行處理，可能導致拖慢 TTFB。但通常這個 trade-off 是值得的，因為 server-side 是我們能控制的，而 Network 速度或是裝置本身的狀況取決於 User 那邊
    - 其他類似的的選擇是 SSG (Static Site Generation) 或 [prerendering](https://web.dev/articles/rendering-on-the-web#terminology)，是在 build time 就去產生 HTML 頁面而不是使用者 request 的時候才去生成。如果架構允許 prerendering，那通常會是較佳的效能選擇
- 拆解 Long Tasks
    - 就算上面的建議都解決了，通常會遇到的問題是當載入龐大的 JavaScript 時，需要載主執行緒上面被解析和執行。這也代表就算 image resource 都已經載好了，還是有可能需要等到不相關的 script 執行完後才能 render
    - 目前市面上的瀏覽器都將 image 的渲染放在主執行緒上執行，因此任何堵住主執行緒的工作都可能造成多餘的 Element Render Delay

### 減少 Resource Load Duration
> Goal：減少將 Resource bytes 由 network 到達 user device 的時間

- 減少資源大小
    - 通常 LCP Resource 會是 image 或是 web font
    - 底下幾種方式會針對這兩種進行處理
        - [提供最佳圖片大小](https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images?hl=zh-tw)
        - [使用新型圖片格式](https://developer.chrome.com/docs/lighthouse/performance/uses-webp-images?hl=zh-tw)
        - [壓縮圖片](https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images?hl=zh-tw)
        - [縮小網頁字型大小](https://web.dev/articles/reduce-webfont-size?hl=zh-tw)
- 減少資源傳輸的距離
    - 將伺服器盡可能設在離目標課群較近的位置
    - 最好的方式是透過 CDN ([Content Delivery Network](https://web.dev/articles/content-delivery-networks?hl=zh-tw))
    - [圖片 CDN](https://web.dev/articles/image-cdns?hl=zh-tw) 不僅可縮短資源傳輸的距離，還能縮減資源大小，自動實作先前提供的所有縮減大小建議
        :::warning
        雖然這個做法可以有效減少 Load Durations，但透過第三方 Domain 代管圖片會產生額外的連線成本。雖然預先連線至來源可降低部分成本，但最佳做法是從與 HTML 文件相同的來源提供圖片。許多 CDN 都允許將 origin request proxy 到 CDN，如果有此選項可以考慮使用。
        :::
- 減少網路頻寬的爭用
    - 如果同時載入許多其他資源，LCP 資源仍可能需要很多時間才能載入
    - 如果將 LCP 資源設為高的 `fetchpriority` 將能盡快開始載入，且瀏覽器會進涼避免優先順序低的資源和他競爭
    - 如果同時載入許多 `fetchpriority` 資源或是單純載入很多資源，都可能影響 LCP Resource 的載入時間
- 完全消除網路時間（最理想的方式）
    - 如果使用 [有效的快取策略](https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl?authuser=2&hl=zh-tw) 來提供靜態資源，使用者便能快速從快取拿到資源並將載入時間縮減到近乎為 0
    - 如果 LCP Resource 為 web font，除了 [縮減 web font 大小](https://web.dev/articles/reduce-webfont-size?hl=zh-tw) 外，也應考慮是否需要在 web font 載入時 block render。如果將 [`font-display`](https://developer.mozilla.org/docs/Web/CSS/@font-face/font-display) 設為 `auto` 或 `block` 以外的值，那文字會[在載入期間仍會顯示](https://developer.chrome.com/docs/lighthouse/performance/font-display?hl=zh-tw)，且 LCP 不會因額外網路要求而遭到封鎖
    - 如果 LCP Resource 超級小，也可以將他透過 data URL 嵌入，可以消除額外的網路請糗。但這樣做仍 [有所限制](https://calendar.perfplanet.com/2018/performance-anti-patterns-base64-encoding/)，會導致無法快取資源、且在某些情況下會多出額外的 [decode](https://www.catchpoint.com/blog/data-uri) 成本，反而導致顯示時間延遲

### 減少 TTFB (Time to First Byte)
> Goal：盡快提供初始 HTML
- 開發人員通常無法完全控制這個步驟，但這也是最重要的步驟之一，因為它會直接影響後續的每個步驟，後端必須先傳送第一個位元組的內容，前端才會有所動作，因此任何可加快 TTFB 的做法，都會改善其他載入指標
- 造成 TTFB 緩慢的常見原因是訪客經過多次 redirect（來自廣告或是縮短後的連結）才抵達網站，這個部分應盡可能減少
- 另一個常見原因是當快取內容無法從 CDN Edge Server 取得時，都必須導向原始的 Server。這可能發生在有多種不同用來做分析用的 URL parameters 組合（就算是同一頁）
- 更多優化 TTFB 的方式可以參考 [Google](https://web.dev/articles/optimize-ttfb?authuser=2)

## 參考來源
- [web.dev - Largest Contentful Paint (LCP)](https://web.dev/articles/lcp)
- [web.dev - optimize-lcp](https://web.dev/articles/optimize-lcp)