# 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)