Largest Contentful Paint (LCP)

Largest Contentful Paint (LCP)

Updated
Available in: Español, 日本語, 한국어, Português, Rусский, 中文, English
Appears in: Web Vitals|Metrics

Largest Contentful Paint (LCP) is an important, user-centric metric for measuring perceived load speed because it marks the point in the page load timeline when the page's main content has likely loaded—a fast LCP helps reassure the user that the page is useful.

Historically, it's been a challenge for web developers to measure how quickly the main content of a web page loads and is visible to users.

Older metrics like load or DOMContentLoaded are not good because they don't necessarily correspond to what the user sees on their screen. And newer, user-centric performance metrics like First Contentful Paint (FCP) only capture the very beginning of the loading experience. If a page shows a splash screen or displays a loading indicator, this moment is not very relevant to the user.

In the past we've recommended performance metrics like First Meaningful Paint (FMP) and Speed Index (SI) (both available in Lighthouse) to help capture more of the loading experience after the initial paint, but these metrics are complex, hard to explain, and often wrong—meaning they still do not identify when the main content of the page has loaded.

Sometimes simpler is better. Based on discussions in the W3C Web Performance Working Group and research done at Google, we've found that a more accurate way to measure when the main content of a page is loaded is to look at when the largest element was rendered.

What is LCP? #

The Largest Contentful Paint (LCP) metric reports the render time of the largest image or text block visible within the viewport, relative to when the page first started loading.

Good LCP values are 2.5 seconds, poor values are greater than 4.0 seconds and anything in between needs improvement

What is a good LCP score? #

To provide a good user experience, sites should strive to have Largest Contentful Paint of 2.5 seconds or less. To ensure you're hitting this target for most of your users, a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices.

To learn more about the research and methodology behind this recommendation, see: Defining the Core Web Vitals metrics thresholds

What elements are considered? #

As currently specified in the Largest Contentful Paint API, the types of elements considered for Largest Contentful Paint are:

  • <img> elements
  • <image> elements inside an <svg> element
  • <video> elements (the poster image is used)
  • An element with a background image loaded via the url() function (as opposed to a CSS gradient)
  • Block-level elements containing text nodes or other inline-level text elements children.

Note, restricting the elements to this limited set was intentional in order to keep things simple in the beginning. Additional elements (e.g. <svg>, <video>) may be added in the future as more research is conducted.

How is an element's size determined? #

The size of the element reported for Largest Contentful Paint is typically the size that's visible to the user within the viewport. If the element extends outside of the viewport, or if any of the element is clipped or has non-visible overflow, those portions do not count toward the element's size.

For image elements that have been resized from their intrinsic size, the size that gets reported is either the visible size or the intrinsic size, whichever is smaller. For example, images that are shrunk down to a much smaller than their intrinsic size will only report the size they're displayed at, whereas images that are stretched or expanded to a larger size will only report their intrinsic sizes.

For text elements, only the size of their text nodes is considered (the smallest rectangle that encompasses all text nodes).

For all elements, any margin, padding, or border applied via CSS is not considered.

Determining which text nodes belong to which elements can sometimes be tricky, especially for elements whose children includes inline elements and plain text nodes but also block-level elements. The key point is that every text node belongs to (and only to) its closest block-level ancestor element. In spec terms: each text node belongs to the element that generates its containing block.

When is largest contentful paint reported? #

Web pages often load in stages, and as a result, it's possible that the largest element on the page might change.

To handle this potential for change, the browser dispatches a PerformanceEntry of type largest-contentful-paint identifying the largest contentful element as soon as the browser has painted the first frame. But then, after rendering subsequent frames, it will dispatch another PerformanceEntry any time the largest contentful element changes.

For example, on a page with text and a hero image the browser may initially just render the text—at which point the browser would dispatch a largest-contentful-paint entry whose element property would likely reference a <p> or <h1>. Later, once the hero image finishes loading, a second largest-contentful-paint entry would be dispatched and its element property would reference the <img>.

It's important to note that an element can only be considered the largest contentful element once it has rendered and is visible to the user. Images that have not yet loaded are not considered "rendered". Neither are text nodes using web fonts during the font block period. In such cases a smaller element may be reported as the largest contentful element, but as soon as the larger element finishes rendering, it'll be reported via another PerformanceEntry object.

In addition to late-loading images and fonts, a page may add new elements to the DOM as new content becomes available. If any of these new elements is larger than the previous largest contentful element, a new PerformanceEntry will also be reported.

If an element that is currently the largest contentful element is removed from the viewport (or even removed from the DOM), it will remain the largest contentful element unless a larger element is rendered.

Prior to Chrome 88, removed elements were not considered as largest contentful elements, and removing the current candidate would trigger a new largest-contentful-paint entry to be dispatched. However, due to popular UI patterns such as image carousels that often removed DOM elements, the metric was updated to more accurately reflect what users experience. See the CHANGELOG for more details.

The browser will stop reporting new entries as soon as the user interacts with the page (via a tap, scroll, or keypress), as user interaction often changes what's visible to the user (which is especially true with scrolling).

For analysis purposes, you should only report the most recently dispatched PerformanceEntry to your analytics service.

Caution: Since users can open pages in a background tab, it's possible that the largest contentful paint will not happen until the user focuses the tab, which can be much later than when they first loaded it.

Load time vs. render time #

For security reasons, the render timestamp of images is not exposed for cross-origin images that lack the Timing-Allow-Origin header. Instead, only their load time is exposed (since this is already exposed via many other web APIs).

The usage example below shows how to handle elements whose render time is not available. But, when possible, it's always recommended to set the Timing-Allow-Origin header, so your metrics will be more accurate.

How are element layout and size changes handled? #

To keep the performance overhead of calculating and dispatching new performance entries low, changes to an element's size or position do not generate new LCP candidates. Only the element's initial size and position in the viewport is considered.

This means images that are initially rendered off-screen and then transition on-screen may not be reported. It also means elements initially rendered in the viewport that then get pushed down, out of view will still report their initial, in-viewport size.

Examples #

Here are some examples of when the Largest Contentful Paint occurs on a few popular websites:

Largest Contentful Paint timeline from cnn.com Largest Contentful Paint timeline from techcrunch.com

In both of the timelines above, the largest element changes as content loads. In the first example, new content is added to the DOM and that changes what element is the largest. In the second example, the layout changes and content that was previously the largest is removed from the viewport.

While it's often the case that late-loading content is larger than content already on the page, that's not necessarily the case. The next two examples show the Largest Contentful Paint occurring before the page fully loads.

Largest Contentful Paint timeline from instagram.com Largest Contentful Paint timeline from google.com

In the first example, the Instagram logo is loaded relatively early and it remains the largest element even as other content is progressively shown. In the Google search results page example, the largest element is a paragraph of text that is displayed before any of the images or logo finish loading. Since all the individual images are smaller than this paragraph, it remains the largest element throughout the load process.

In the first frame of the Instagram timeline, you may notice the camera logo does not have a green box around it. That's because it's an <svg> element, and <svg> elements are not currently considered LCP candidates. The first LCP candidate is the text in the second frame.

How to measure LCP #

LCP can be measured in the lab or in the field, and it's available in the following tools:

Field tools #

Lab tools #

Measure LCP in JavaScript #

To measure LCP in JavaScript, you can use the Largest Contentful Paint API. The following example shows how to create a PerformanceObserver that listens for largest-contentful-paint entries and logs them to the console.

new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log('LCP candidate:', entry.startTime, entry);
}
}).observe({type: 'largest-contentful-paint', buffered: true});

Warning:

This code shows how to log largest-contentful-paint entries to the console, but measuring LCP in JavaScript is more complicated. See below for details:

In the above example, each logged largest-contentful-paint entry represents the current LCP candidate. In general, the startTime value of the last entry emitted is the LCP value—however, that is not always the case. Not all largest-contentful-paint entries are valid for measuring LCP.

The following section lists the differences between what the API reports and how the metric is calculated.

Differences between the metric and the API #

  • The API will dispatch largest-contentful-paint entries for pages loaded in a background tab, but those pages should be ignored when calculating LCP.
  • The API will continue to dispatch largest-contentful-paint entries after a page has been backgrounded, but those entries should be ignored when calculating LCP (elements may only be considered if the page was in the foreground the entire time).
  • The API does not report largest-contentful-paint entries when the page is restored from the back/forward cache, but LCP should be measured in these cases since users experience them as distinct page visits.
  • The API does not consider elements within iframes, but to properly measure LCP you should consider them. Sub-frames can use the API to report their largest-contentful-paint entries to the parent frame for aggregation.

Rather than memorizing all these subtle differences, developers can use the web-vitals JavaScript library to measure LCP, which handles these differences for you (where possible):

import {getLCP} from 'web-vitals';

// Measure and log LCP as soon as it's available.
getLCP(console.log);

You can refer to the source code for getLCP() for a complete example of how to measure LCP in JavaScript.

In some cases (such as cross-origin iframes) it's not possible to measure LCP in JavaScript. See the limitations section of the web-vitals library for details.

What if the largest element isn't the most important? #

In some cases the most important element (or elements) on the page is not the same as the largest element, and developers may be more interested in measuring the render times of these other elements instead. This is possible using the Element Timing API, as described in the article on custom metrics.

How to improve LCP #

LCP is primarily affected by four factors:

  • Slow server response times
  • Render-blocking JavaScript and CSS
  • Resource load times
  • Client-side rendering

For a deep dive on how to improve LCP, see Optimize LCP. For additional guidance on individual performance techniques that can also improve LCP, see:

Additional resources #

CHANGELOG #

Occasionally, bugs are discovered in the APIs used to measure metrics, and sometimes in the definitions of the metrics themselves. As a result, changes must sometimes be made, and these changes can show up as improvements or regressions in your internal reports and dashboards.

To help you manage this, all changes to either the implementation or definition of these metrics will be surfaced in this CHANGELOG.

Last updated: Improve article