Times really flies. Since the last article on improving FCP, it has been almost half a year. In this article, we’ll explore how to improve Largest Contentful Paint (LCP).
Largest Contentful Paint (LCP) is one of the three Core Web Vitals metrics, and it represents how quickly the main content of a web page is loaded. Specifically, LCP measures the time from when the user initiates loading the page until the largest image or text block is rendered within the viewport.
As currently 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)url()
 function, (as opposed to a CSS gradient)Before optimizing LCP, developers should seek to understand if they even have an LCP issue, and the extent of any such issue.
LCP can be measured in a number of tools and not all of these measure LCP in the same way. To understand LCP of real users, we should look at what real users are experiencing, rather than what a lab-based tool like Lighthouse or local testing shows. These lab-based tools can give a wealth of information to explain and help you improve LCP, but be aware that lab tests alone may not be entirely representative of what your actual users experience.
LCP data based on real users can be surfaced from Real User Monitoring (RUM) tools installed on a site, or by using the Chrome User Experience Report (CrUX) which collect anonymous data from real Chrome users for millions of websites
The Performance panel of Chrome DevTools shows your local LCP experience next to the page or origin’s CrUX LCP in the live metrics view.
To provide a good user experience, sites should strive to have an LCP of 2.5
seconds or less for at least 75% of page visits.
Now that we know how to get the first bytes out the door, we can make some infrastructure changes to ensure that we’re delivering our bytes as quickly as possible.
But how do we ensure that the bytes we’re sending to load the page are the right ones to reach this point of completion as soon as possible? As a reminder, the Largest Contentful Paint (LCP) is the time between the start of the page load and when the user sees that most of the page is ready, or almost ready.
Let’s look at the sequence of events in a simplified version of what we would see in the performance panel of Google DevTools. At the beginning of a request, the first thing we need to do is fetch the HTML document. We need to pull down the initial document and have the browser parse it to determine what other resources are needed.
If we attach our metrics here, the First Contentful Paint (FCP) occurs right after the HTML is downloaded and the first elements are rendered on the screen. However, the LCP happens only after all other resources, such as images, JavaScript, and CSS, have been fully loaded and rendered.
So how do we achieve a faster LCP? We do this by reducing the number of tasks the browser needs to perform and focusing on delivering the user’s most important content as quickly as possible.
For a well-optimized page, you want your LCP resource request to start loading as early as it can, and you want the LCP element to render as quickly as possible after the LCP resource finishes loading. To help visualize whether or not a particular page is following this principle, you can break down the total LCP time into the following sub-parts:
The goal in this step is to ensure the LCP resource starts loading as early as possible. While in theory the earliest a resource could start loading is immediately after TTFB, in practice there is always some delay before browsers actually start loading resources.
A good rule of thumb is that your LCP resource should start loading at the same time as the first resource loaded by that page. Or, to put that another way, if the LCP resource starts loading later than the first resource, then there’s opportunity for improvement.
Generally speaking, there are two factors that affect how quickly an LCP resource can be loading:
Optimize when the resource is discovered
To ensure your LCP resource starts loading as early as possible, it’s critical that the resource is discoverable in the initial HTML document response by the browser’s preload scanner. For example, in the following cases, the browser can discover the LCP resource by scanning the HTML document response:
<img>
element, and its src or srcset attributes are present in the initial HTML markup.<link rel="preload">
in the HTML markup (or using a Link header).<link rel="preload">
in the HTML markup (or using a Link header).Here are some examples where the LCP resource cannot be discovered from scanning the HTML document response:
<img>
that is dynamically added to the page using JavaScript.data-src
or data-srcset
).In each of these cases, the browser needs to run the script or apply the stylesheet—which usually involves waiting for network requests to finish—before it can discover the LCP resource and could start loading it. This is never optimal.
To eliminate unnecessary resource load delay, your LCP resource should be discoverable from the HTML source. In cases where the resource is only referenced from an external CSS or JavaScript file, the LCP resource should be preloaded with a high fetch priority, for example:
<!-- 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"
/>
Optimize the priority the resource is given
Even if the LCP resource is discoverable from the HTML markup, it still may not start loading as early as the first resource. This can happen if the browser preload scanner’s priority heuristics don’t recognize that the resource is important, or if it determines that other resources are more important.
For example, you can delay your LCP image using HTML if you set loading="lazy"
on your <img>
element. Using lazy loading means that the resource won’t be loaded until after layout confirms the image is in the viewport and so may begin loading later than it otherwise would.
Even without lazy loading, images are not initially loaded with the highest priority by browsers as they are not render-blocking resources. You can hint to the browser as to which resources are most important using the fetchpriority attribute for resources that could benefit from a higher priority:
<img fetchpriority="high" src="/path/to/hero-image.webp" />
After you have optimized your LCP resource priority and discovery time, your network waterfall should look like this (with the LCP resource starting at the same time as the first resource):
The goal in this step is to ensure the LCP element can render immediately after its resource has finished loading, no matter when that happens.
The primary reason the LCP element wouldn’t be able to render immediately after its resource finishes loading is if rendering is blocked for some other reason:
stylesheets
or synchronous scripts
in the <head>
that are still loading.Defer or inline render-blocking JavaScript
It is almost never necessary to add synchronous scripts (scripts without the async
or defer
attributes) to the <head>
of your pages, and doing so will almost always have a negative impact on performance.
In cases where JavaScript code needs to run as early as possible in the page load, it’s best to inline it so rendering isn’t delayed waiting on another network request. As with stylesheets, though, you should only inline scripts if they’re very small.
❌ 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>
The goal of this step is to reduce the time spent transferring the bytes of the resource over the network to the user’s device. In general, there are four ways to do that:
font-display: swap
Use modern image formats.
The LCP resource of a page (if it has one) will either be an image or a web font. The following guides go into great detail about how to reduce the size of both:
AVIF and WebP are image formats that have superior compression and quality characteristics compared to their older JPEG and PNG counterparts. Encoding your images in these formats rather than JPEG or PNG means that they will load faster and consume less cellular data.
AVIF is supported in Chrome, Firefox, and Opera and offers smaller file sizes compared to other formats with the same quality settings.
WebP is supported in the latest versions of Chrome, Firefox, Safari, Edge, and Opera and provides better lossy and lossless compression for images on the web
Image CDNs
Image CDNs are a type of CDN that specializes in serving images. They can help reduce the distance the resource has to travel by caching images closer to the user and provide additional features like image optimization and resizing.
Lazy load images
Lazy loading images means that images are only loaded when they are in the viewport. This can reduce contention for network bandwidth by ensuring that images are only loaded when they are needed.
LCP is complex, and its timing can be affected by a number of factors. But if you consider that optimizing LCP is primarily about optimizing the load of the LCP resource, it can significantly simplify things.
At a high level, optimizing LCP can be summarized in a few steps: