Largest Contentful Paint (LCP) is a stable Core Web Vital metric for measuring perceived load speed. 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 don't work well 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 isn't 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 don't identify when the main content of the page has loaded.
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 is rendered.
What is LCP?
LCP reports the render time of the largest image or text block visible in the viewport, relative to when the user first navigated to the page.
What is a good LCP score?
To provide a good user experience, sites should strive to have LCP 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.
What elements are considered?
As specified in the Largest Contentful Paint API, the types of elements considered for Largest Contentful Paint are:
<img>
elements (the first frame presentation time is used for animated content such as GIFs or animated PNGs)<image>
elements inside an<svg>
element<video>
elements (the poster image load time or first frame presentation time for videos is used—whichever is earlier)- An element with a background image loaded using the
url()
function, (as opposed to a CSS gradient) - Block-level elements containing text nodes or other inline-level text element children.
Restricting the elements to this limited set was intentional to reduce complexity.
Additional elements (like the full <svg>
support) may be added in the future
as more research is conducted.
As well as only considering some elements, LCP measurements use heuristics to exclude certain elements that users are likely to see as "non-contentful". For Chromium-based browsers, these include:
- Elements with an opacity of 0, making them invisible to the user.
- Elements that cover the full viewport, that are likely to be background elements.
- Placeholder images or other images with a low entropy, that likely don't reflect the true content of the page.
Browsers are likely to continue to improve these heuristics to ensure we match user expectations of what the largest contentful element is.
These "contentful" heuristics differ from those used by FCP, which might consider some of these elements, such as placeholder images or full viewport images, even if they're ineligible to be LCP candidates. Despite both using "contentful" in their name, the aim of these metrics is different. FCP measures when any content is painted to screen, whereas LCP measures when the main content is painted.
How is an element's size determined?
The size of the element reported for LCP is typically the size that's visible to the user within the viewport. If the element extends outside the viewport, or if any of the element is clipped or has non-visible overflow, those portions don't 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 text elements, LCP considers only the smallest rectangle that can contain all text nodes.
For all elements, LCP doesn't consider margins, paddings, or borders applied using CSS.
When is LCP reported?
Web pages often load in stages, and as a result, the largest element on the page might change during loading.
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. After rendering
subsequent frames, it dispatches another
PerformanceEntry
any time the largest contentful element changes.
For example, on a page with text and a hero image, the browser might initially
render only the text, and the browser would dispatch a largest-contentful-paint
entry whose element
property references a <p>
or <h1>
. After the hero
image finishes loading, a second largest-contentful-paint
entry is dispatched,
with an element
property referencing the <img>
.
An element can only be considered the largest contentful element after it has
rendered and is visible to the user. Images that haven't yet loaded aren't
considered "rendered". Neither are text nodes using web fonts during the
font block period.
In such cases, a smaller element might be reported as the largest contentful
element, but as soon as the larger element finishes rendering, another
PerformanceEntry
is created.
In addition to late-loading images and fonts, a page might 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
is created.
If the largest contentful element is removed from the viewport, or even from the DOM, it remains the largest contentful element unless a larger element is rendered.
The browser stops reporting new entries as soon as the user interacts with the page (through a tap, scroll, or keypress), because user interaction often changes what's visible to the user (especially when scrolling).
For analysis purposes, report only the most recently dispatched
PerformanceEntry
to your analytics service.
Load time versus 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, which other APIs already expose, is
available.
This can lead to the seemingly impossible situation where LCP is reported by web APIs as earlier than FCP. This is only because of this security restriction, and it doesn't represent what's really happening.
When possible, we always recommend setting the
Timing-Allow-Origin
header to make your metrics 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 don't 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 might not be reported. It also means elements initially rendered in the viewport that then get pushed out of view still report their initial in-viewport size.
Examples
Here are some examples of when the Largest Contentful Paint occurs on a few popular websites:
In both of these timelines, the largest element (highlighted in green) changes as content loads. In the first example, new content is added to the DOM, changing what element is the largest. In the second example, the layout changes, removing a previous largest content element from the viewport.
While late-loading content is often larger than content already on the page, that's not necessarily the case. The next two examples show the LCP happening before the page fully loads.
In the first example, the Instagram logo loads relatively early and remains the largest element even as other content is added. In the Google Search results page example, the largest element is a paragraph of text that displays before any of the images or the logo finish loading. Because each individual image is smaller than this paragraph, it remains the largest element throughout the load process.
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
- Chrome User Experience Report
- PageSpeed Insights
- Search Console (Core Web Vitals report)
web-vitals
JavaScript library
Lab tools
Measure LCP in JavaScript
To measure LCP in JavaScript, 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});
In the previous 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, 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 dispatches
largest-contentful-paint
entries for pages loaded in a background tab, but those pages should be ignored when calculating LCP. - The API continues to dispatch
largest-contentful-paint
entries after a page has been backgrounded, but those entries should be ignored when calculating LCP. Elements can only be considered if the page was in the foreground the entire time. - The API doesn't report
largest-contentful-paint
entries when the page is restored from the back/forward cache, but LCP should be measured in these cases because users experience them as distinct page visits. - The API doesn't consider elements within iframes, but the metric does because
they're part of the user experience of the page. In pages with an LCP within
an iframe—for example a poster image on an embedded video—this will
show as a difference between CrUX and RUM.
To measure LCP properly, you must include iframes. Sub-frames can use the API to report
their
largest-contentful-paint
entries to the parent frame for aggregation. - The API measures LCP from navigation start. For
prerendered pages,
measure LCP from
activationStart
instead, because that corresponds to the LCP time as experienced by the user.
Instead of memorizing all these subtle differences, we recommend that developers
use the web-vitals
JavaScript library
to measure LCP, which handles most of these differences for you. (It doesn't
cover the iframe issue.)
import {onLCP} from 'web-vitals';
// Measure and log LCP as soon as it's available.
onLCP(console.log);
Refer to the source code for onLCP()
for a complete example of how to measure LCP in JavaScript.
What if the largest element isn't the most important?
In some cases the most important element (or elements) on the page isn't the same as the largest element, and developers might 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
A full guide on optimizing LCP is available to guide you through the process of identifying LCP timings in the field and using lab data to drill down and optimize them.
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 is surfaced in this changelog.
If you have feedback for these metrics, provide it in the web-vitals-feedback Google group.