How to render your main content faster.
Updated
I can’t see any useful content! Why does it take so long to load? 😖
One factor contributing to a poor user experience is how long it takes a user to see any content
rendered to the screen. First Contentful Paint (FCP) measures how long it
takes for initial DOM content to render, but it does not capture how long it took the largest
(usually more meaningful) content on the page to render.
Largest Contentful Paint (LCP) is a Core Web
Vitals metric and measures when the largest content element in the
viewport becomes visible. It can be used to determine when the main content of the page has finished
rendering on the screen.
The most common causes of a poor LCP are:
Slow server response times
The longer it takes a browser to receive content from the server, the longer it takes to render
anything on the screen. A faster server response time directly improves every single page-load
metric, including LCP.
Before anything else, improve how and where your server handles your content. Use Time to First
Byte (TTFB) to measure your server response times. You can
improve your TTFB in a number of different ways:
- Optimize your server
- Route users to a nearby CDN
- Cache assets
- Serve HTML pages cache-first
- Establish third-party connections early
Optimize your server
Are you running expensive queries that take your server a significant amount of time to complete? Or
are there other complex operations happening server-side that delay the process to return page
content? Analyzing and improving the efficiency of your server-side code will directly improve the
time it takes for the browser to receive the data.
Instead of just immediately serving a static page on a browser request, many server-side web
frameworks need to create the web page dynamically. In other words, rather than just sending a
complete HTML file that’s already ready when the browser requests it, the frameworks need to run
logic to construct the page. This could be due to pending results from a database query or even
because components need to be generated into markup by a UI framework (such as
React). Many web frameworks that run on the server
have performance guidance that you can use to speed up this process.
Route users to a nearby CDN
A Content Delivery Network (CDN) is a network of servers distributed in many different locations. If
the content on your web page is being hosted on a single server, your website will load slower for
users that are geographically farther away because their browser requests literally have to travel
around the world. Consider using a CDN to ensure that your users never have to wait for network
requests to faraway servers.
Cache assets
If your HTML is static and doesn’t need to change on every request, caching can prevent it from
being recreated unnecessarily. By storing a copy of the generated HTML on disk, server-side caching
can reduce TTFB and minimize resource usage.
Depending on your toolchain, there are many different ways to apply server caching:
- Configure reverse proxies (Varnish,
nginx) to serve cached content or act as a cache server when
installed in front of an application server - Configure and manage your cloud provider’s
(Firebase,
AWS,
Azure) cache behavior - Use a CDN that provides edge servers so that your content is cached and stored closer to
your users
Serve HTML pages cache-first
When installed, a service
worker runs in the browser
background and can intercept requests from the server. This level of programmatic cache control
makes it possible to cache some or all of the HTML page’s content and only update the cache when the
content has changed.
The following chart shows how LCP distributions have been reduced on a site using this pattern:

The chart shows the distribution for LCP from a single site over the last 28 days, segmented by
service worker state. Notice how far more page loads have a faster LCP value after cache-first HTML
page serving was introduced in the service worker (blue portion of chart).
Establish third-party connections early
Server requests to third-party origins can also impact LCP, especially if they’re needed to display
critical content on the page. Use rel="preconnect"
to inform the browser that your page intends to
establish a connection as soon as possible.
<link rel="preconnect" href="https://example.com">
You can also use dns-prefetch
to resolve DNS lookups faster.
<link rel="dns-prefetch" href="https://example.com">
Although both hints work differently, consider using dns-prefetch
as a fallback for browsers that
do not support preconnect
.
<head>
…
<link rel="preconnect" href="https://example.com">
<link rel="dns-prefetch" href="https://example.com">
</head>
Render blocking JavaScript and CSS
Before a browser can render any content, it needs to parse HTML markup into a DOM tree. The HTML
parser will pause if it encounters any external stylesheets (<link rel="stylesheet">
) or synchronous
JavaScript tags (<script src="main.js">
).
Scripts and stylesheets are both render blocking resources which delay FCP, and consequently LCP.
Defer any non-critical JavaScript and CSS to speed up loading of the main content of your web page.
Reduce CSS blocking time
Ensure that only the minimal amount of necessary CSS is blocking render on your site with the
following:
- Minify CSS
- Defer non-critical CSS
- Inline critical CSS
Minify CSS
For easier legibility, CSS files can contain characters such as spacing, indentation, or comments.
These characters are all unnecessary for the browser, and minifying these files will ensure that
they get removed. Ultimately, reducing the amount of blocking CSS will always improve the time it
takes to fully render the main content of the page (LCP).
If you use a module bundler or build tool, include an appropriate plugin to minify CSS files on
every build:

Defer non-critical CSS
Use the Coverage tab in Chrome
DevTools to find any unused CSS on your web page.
To optimize:
-
Remove any unused CSS entirely or move it to another stylesheet if used on a separate
page of your site. -
For any CSS not needed for initial rendering, use
loadCSS to load files
asynchronously, which leveragesrel="preload"
andonload
.<link rel="preload" href="stylesheet.css" as="style" onload="this.rel='stylesheet'">

Inline critical CSS
Inline any critical-path CSS used for above-the-fold content by including it directly in <head>.

Inlining important styles eliminates the need to make a round-trip request to fetch critical CSS.
Deferring the rest minimizes CSS blocking time.
If you cannot manually add inline styles to your site, use a library to automate the process. Some
examples:

Reduce JavaScript blocking time
Download and serve the minimal amount of necessary JavaScript to users. Reducing the amount of
blocking JavaScript results in a faster render, and consequently a better LCP.
This can be accomplished by optimizing your scripts in a few different ways:
Slow resource load times
Although an increase in CSS or JavaScript blocking time will directly result in worse performance,
the time it takes to load many other types of resources can also affect paint times. The types of
elements that affect LCP are:
<img>
elements<image>
elements inside an<svg>
element<video>
elements (if specified, the
poster image is
used to measure LCP)- 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
The time it takes to load these elements if rendered above-the-fold will have a direct effect on
LCP. There are a few ways to ensure these files are loaded as fast as possible:
- Optimize and compress images
- Preload important resources
- Compress text files
- Deliver different assets based on network connection (adaptive serving)
- Cache assets using a service worker
Optimize and compress images
For many sites, images are the largest element in view when the page has finished loading. Hero
images, large carousels or banner images are all common examples of this.

Improving how long it takes to load and render these types of images will directly speed up LCP. To
do this:
- Consider not using an image in the first place. If it’s not relevant to the content,
remove it. - Compress images (with Imagemin for example)
- Convert images into newer formats (JPEG 2000, JPEG XR, or WebP)
- Use responsive images
- Consider using an image CDN
Take a look at Optimize your
images for guides and resources that explain all of these techniques in detail.
Preload important resources
At times, important resources that are declared or used in a certain CSS or JavaScript file may be
fetched later than you would like, such as a font tucked deep in one of the many CSS files of an
application.
If you know that a particular resource should be prioritized, use <link rel="preload">
to fetch it
sooner. Many types of
resources
can be preloaded, but you should first focus on preloading critical
assets, such as fonts, above-the-fold images or videos,
and critical-path CSS or JavaScript.
<link rel="preload" as="script" href="script.js">
<link rel="preload" as="style" href="style.css">
<link rel="preload" as="image" href="img.png">
<link rel="preload" as="video" href="vid.webm" type="video/webm">
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
Since Chrome 73, preloading can be used along with responsive images to combine both
patterns for much faster image loading.
<link
rel="preload"
as="image"
href="wolf.jpg"
imagesrcset="wolf_400px.jpg 400w, wolf_800px.jpg 800w, wolf_1600px.jpg 1600w"
imagesizes="50vw"
>
Compress text files
Compression algorithms, like
Gzip and
Brotli, can
significantly reduce the size of text files (HTML, CSS, JavaScript) as they’re transferred between
the server and browser. Gzip is effectively supported in all browsers and Brotli, which provides
even better compression results, can be used in almost all newer
browsers.
Compressing your resources will minimize their delivery size, improving load times and consequently
LCP.
- First, check if your server already compresses files automatically. Most hosting
platforms, CDNs, and reverse proxy servers either encode assets with compression by default or
allow you to easily configure them. - If you need to modify your server to compress files, consider using Brotli instead of gzip
since it can provide better compression ratios. - Once you pick a compression algorithm to use, compress assets ahead of time during the build
process instead of on-the-fly as they are requested by the browser. This minimizes server
overhead and prevents delays when requests are made, especially when using high compression ratios.

Adaptive serving
When loading resources that make up the main content of a page, it can be effective to conditionally
fetch different assets depending on the user’s device or network conditions. This can be done using
the Network Information, Device
Memory, and
HardwareConcurrency
APIs.
If you have large assets that are critical for initial rendering, you can use different variations
of the same resource depending on the user’s connection or device. For example, you can display an
image instead of a video for any connection speeds lower than 4G:
if (navigator.connection && navigator.connection.effectiveType) {
if (navigator.connection.effectiveType === '4g') {
} else {
}
}
A list of useful properties that you can use:
navigator.connection.effectiveType
: Effective connection typenavigator.connection.saveData
: Data-saver enabled/disablednavigator.hardwareConcurrency
: CPU core countnavigator.deviceMemory
: Device Memory
Cache assets using a service worker
Service workers can be used for many useful tasks, including serving smaller HTML responses as
mentioned earlier in this article. They can also be used to cache any static resource which can be
served to the browser instead of from the network on repeat requests.
Precaching critical resources using a service worker can reduce their load times significantly,
especially for users who reload the web page with a weaker connection (or even access it offline).
Libraries like Workbox can make the process of
updating precached assets easier than writing a custom service worker to handle this yourself.
Client-side rendering
Many sites use client-side JavaScript logic to render pages directly in the browser. Frameworks and
libraries, like React, Angular, and
Vue, have made it easier to build single-page applications that handle
different facets of a web page entirely on the client rather on the server.
If you’re building a site that is mostly rendered on the client, you should be wary of the effect it
can have on LCP if a large JavaScript bundle is used. If optimizations aren’t in place to prevent
it, users may not see or interact with any content on the page until all the critical JavaScript has
finished downloading and executing.
When building a client-side rendered site, consider the following optimizations:
- Minimize critical JavaScript
- Use server-side rendering
- Use pre-rendering
Minimize critical JavaScript
If content on your site only becomes visible, or can be interacted with, after a certain amount of
JavaScript is downloaded: it becomes even more important to cut down on the size of your bundle as
much as possible. This can be done by:
- Minifying JavaScript
- Deferring unused JavaScript
- Minimizing unused polyfills
Go back to the Reduce JavaScript blocking time section to read more about
these optimizations.
Use server-side rendering
Minimizing the amount of JavaScript should always be the first thing to focus on for sites that are
mostly client-rendered. However, you should also consider combining a server rendering experience to
improve LCP as much as possible.
This concept works by using the server to render the application into HTML, where the client then
«hydrates» all the JavaScript and required data
onto the same DOM content. This can improve LCP by ensuring the main content of the page is first
rendered on the server rather than only on the client, but there are a few drawbacks:
- Maintaining the same JavaScript-rendered application on the server and the client can
increase complexity. - Executing JavaScript to render an HTML file on the server will always increase server
response times (TTFB) as compared to just serving static pages from the server. - A server-rendered page may look like it can be interacted with, but it can’t respond to any
user input until all the client-side JavaScript has executed. In short, it can make Time to
Interactive (TTI) worse.
Use pre-rendering
Pre-rendering is a separate technique that is less complex than server-side rendering and also
provides a way to improve LCP in your application. A headless browser, which is a browser without a
user interface, is used to generate static HTML files of every route during build time. These files
can then be shipped along with the JavaScript bundles that are needed for the application.
With pre-rendering, TTI is still negatively impacted but server response times aren’t as affected as
they would be with a server-side rendering solution that dynamically renders each page only after
it’s requested.

For a deeper dive into different server-rendering architectures, take a look at Rendering on the
web.
A number of tools are available to measure and debug LCP:
-
Lighthouse 6.0 includes support for
measuring LCP in a lab setting. -
The Timings section of the
Performance
panel in Chrome DevTools includes a LCP marker and shows you which element is associated with
LCP when you hover over the Related Node field. -
Chrome User Experience Report
provides real-world LCP values aggregated at the origin-level
With thanks to Philip Walton, Katie Hempenius, Kayce Basques, and Ilya Grigorik for their reviews.