In this guide, I’ll explain how to optimize Largest Contentful Paint using HTML and CSS. I’ll explain what LCP is, then before going into the details, I’ll quickly recap the most important things you need to know about LCP.
So, What is Largest Contentful Paint (LCP)?
Largest Contentful Paint (LCP) is one of the three Core Web Vitals, Google’s web performance metrics that are also included in its search engine algorithm.
As the name suggests, each Web Vital measures the vitality of a website from a different perspective. Largest Contentful Paint quantifies loading performance, or more precisely, the render time of the largest content element above the fold.
With that said, let’s get stuck in! Jump to content in this guide:
Largest Contentful Paint (In a Bit More Detail)
Largest Contentful Paint is a time value that measures how long it takes for the user’s browser to render the largest content element within the viewport.
The largest content element can be:
- a block-level element containing text (e.g. a heading or a paragraph)
- an image added with (1) the
<img>
HTML tag, (2) the<image>
SVG tag within the<svg>
element, or (3) as a CSS background image defined by theurl()
CSS function (excluding gradient backgrounds) - a
<video>
element with a poster image (the render time of the poster image is used)
The results are classified on a traffic-light scale where:
- green 🟩 refers to a good LCP score
- yellow 🟨 means that LCP needs improvement
- and red 🟥 refers to a poor LCP score
To get the green light, the largest content element on a page must load within 2.5 seconds. To at least get the yellow light, it must load within 4 seconds:
Before Getting Started with LCP Optimization
Here are some important things to keep in mind before you start optimizing your website for LCP — see more details on the evaluation and calculation of Largest Contentful Paint in my article on the Core Web Vitals metrics in general:
- Google tracks each page individually for Core Web Vitals, so in theory, you’d need to optimize each page individually to get good results. However, if you use a CMS or framework, e.g. you want to improve LCP on a WordPress site, the best strategy is to optimize web pages by type, such as single posts, archive pages, pages generated by various content plugins, etc.
- Each web page has two (officially three) LCP scores based on device type: the mobile score is used as a ranking factor in the mobile search algorithm, while the desktop score is used as a ranking factor in the desktop search algorithm (Google also tracks a tablet score which is available in the CrUX Dashboard — while this is not used by the search engine as there’s no separate tablet search, it can help with performance optimization and troubleshooting).
- When optimizing your website in the development phase, you’ll measure Largest Contentful Paint using a lab tool such as Lighthouse (this is the lab score — its results vary across tests). However, in the search algorithm, Google uses an aggregated LCP value measured on real users (this is the field score, the rolling average of the previous 28 days), which is different from the lab value.
Without further ado, now let’s see the tips on how to optimize Largest Contentful Paint.
Minify Your CSS and JavaScript Files
Minifying your CSS and JavaScript files is a relatively simple way to improve your LCP results.
Depending on your stack and the characteristics of your audience (e.g. the connection type they typically use), you can use different minification strategies. As nowadays most network connections happen over HTTP/2 or HTTP/3, you don’t necessarily need to bundle everything into one file, as both of these communication protocols support stream multiplexing (the user’s browser can download more than one file at the same time).
There are many CSS and JavaScript minifier tools you can choose from, including:
Inline Critical CSS in the Head
Critical CSS refers to the style rules that apply to the content that appears above the fold.
Since LCP measures the render time of the largest content element above the fold, you can improve it by extracting the critical CSS and placing it into the <head>
section as inline CSS enclosed by a pair of <style></style>
tags.
The main advantage of this technique is that the browser can render above-the-fold content without having to wait for the entire CSS file to be parsed (this can matter a lot under poor network conditions).
As you need to optimize your code for various device types, from small-screen smartphones to larger monitors, there are no strict rules about which elements qualify as above-the-fold and below-the-fold content. If you want to extract critical CSS manually, you’ll need to work with estimates (e.g. most likely, you’ll need to add all or most of the typography rules that apply to visible content such as headings, paragraphs, etc.).
Alternatively, you can use an online critical CSS generator, such as:
How to Optimize Non-Critical CSS?
Now that you have extracted and inlined critical CSS, you may want to ask what to do with the non-critical part of your CSS code.
Ultimately, the goal is to delay the rendering of non-critical CSS. However, the <link>
tag used for loading stylesheets doesn’t have a defer
attribute like the <script>
tag used for JavaScript.
The best workaround to defer non-critical CSS is by:
-
preloading the CSS file using the
rel="preload"
andas="style"
attributes - (at the same time) adding an extra
rel="stylesheet"
attribute to anonload
event handler
This preload-onload technique makes the browser process the CSS file only after the page content has loaded (but as the file is preloaded, it’s already downloaded, so the browser can start to process it immediately):
1 |
<!DOCTYPE html>
|
2 |
<html lang="en"> |
3 |
<head>
|
4 |
<!-- meta tags -->
|
5 |
<title>Inline Critical CSS</title> |
6 |
<style><!-- critical CSS inlined and minified --></style> |
7 |
<link rel="preload" href="style.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> |
8 |
<noscript><link rel="stylesheet" href="style.css"></noscript> |
9 |
</head>
|
10 |
<body>
|
11 |
|
12 |
</body>
|
13 |
</html>
|
The code example above also nulls the onload
event handler before adding the rel="stylesheet"
attribute, which is required by some browsers (to prevent the handler from being recalled once the value of the rel
attribute changes). The <noscript>
tag serves as a fallback, as the onload
attribute only works when JavaScript is enabled in the user’s browser.
You can further improve CSS performance by using the media
attribute to load some of the non-critical CSS (e.g. mobile and print styles) conditionally — check out this article for more details.
Defer or Async Non-Critical JavaScript + Inline Critical JavaScript
Now, let’s see how you can optimize the loading of your JavaScript resources to improve Largest Contentful Paint.
Defer Non-Critical Scripts
With the defer
attribute, you can delay the download of non-critical scripts that either run below the fold or are triggered by user action so that the browser can render the largest content element faster.
You can add the defer
attribute to the <script>
tag in the following way:
1 |
<script defer src="deferred-script.js"></script> |
When the defer
attribute is added to the <script>
tag, it’s only executed once the HTML page has been parsed but still before the DOMContentLoaded
event.
Async Non-Critical Scripts
The async
attribute is an alternative to defer
. It serves to load scripts asynchronously (independently from the DOM).
Unlike defer
, it’s not tied to the DOMContentLoaded
event. It executes as soon as possible, but, like defer
, at a low priority. It’s typically added to third-party scripts.
You can use it in the following way:
1 |
<script async src="asynchronous-script.js"></script> |
Both the defer
and async
attributes turn your JavaScript file into a non-render-blocking resource.
Note that you can’t add the defer
and async
attributes to inline scripts—they only work when the src
attribute is present.
Inline Critical Scripts
Critical JavaScript is a render-blocking script that includes functionality that the browser needs to load before it renders the page.
While most scripts can be deferred or loaded asynchronously, if you still have a critical script, add it as inline JavaScript enclosed by a pair of <script></script>
tags to the <head>
section of your HTML page.
Optimize Your Images
Above-the-fold images are LCP candidates. Even though text nodes (e.g. headings) also qualify, the largest image in the viewport has a good chance of being the LCP element.
Overall, the goal of image optimization is to make LCP candidate images load fast while deferring images that will likely load below the fold.
Note that the LCP element is not necessarily the same element on every device. Only above-the-fold text nodes and images are taken into account which can vary from viewport to viewport (e.g. a small screen will show fewer images and content above the fold than a large monitor).
To optimize your images for LCP, you can use one or more of the following techniques:
1. Compress Your Images
These days, you have access to various image compression tools, including standalone web apps and APIs, such as Squoosh, TinyPNG, and ImageOptim, and content management systems also have their own solutions, e.g. WPCompress and other plugins for WordPress.
The important thing about image compression is that you need to find the balance between the compression ratio and image quality, as over-optimizing images can result in blurry or low-quality images that can harm the user experience.
2. Use the Lossy Versions of Next-Generation Image Formats
Next-generation image formats compress images using advanced algorithms so that they can provide the same or better image quality in a smaller file size than traditional image formats such as JPEG.
WebP is currently the most widely supported next-gen image format; it’s supported by every modern browser. AVIF is a newer format that results in even smaller image files—however, it’s supported by fewer browsers (e.g. Edge doesn’t support it).
You can use some of the image optimization apps listed above (e.g. Squoosh) to convert your images to next-gen images.
Note, however, that only the lossy algorithms of the WebP and AVIF image formats come with definitely more performant results than their JPG equivalents. Their lossless algorithms (used for PNG compression) sometimes result in a larger image file than its PNG equivalent—see how the lossy vs lossless compression of next-gen images works in detail.
You can add an AVIF image with WebP and JPG fallbacks using the <picture>
and <source>
HTML elements:
1 |
<picture>
|
2 |
|
3 |
<source srcset="image.avif" type="image/avif"> |
4 |
|
5 |
<source srcset="image.webp" type="image/webp"> |
6 |
|
7 |
<img src="image.jpg" alt="Image" width="900" height="600"> |
8 |
|
9 |
</picture>
|
If you only want to add the WebP format, use just the second <source>
tag in the code example above. Also note that you don’t need the JPG fallback if you don’t need to support legacy browsers — in this case, you can simply add the WebP image to the <img>
element.
3. Implement Responsive Images
The code snippet above is one example of the responsive image syntax. However, HTML and CSS offer various ways to implement responsive images.
Responsive images mean that you define more than one source file for each <img>
element on the page, and the browser chooses the one that best fits the present conditions (e.g. the alternative sources can differ in format like in the WebP/AVIF example above, size, resolution, direction, etc.).
For example, you can use the srcset
and sizes
attributes to load smaller images on mobile screens — which will improve your mobile LCP scores:
1 |
<img src="image-sm.webp" srcset="image-sm.webp 400w, image-md.webp 800w, |
2 |
images-lg.webp 1200w" sizes="80vw" alt="Image"> |
4. Lazy Load Out-Of-View Images + Preload LCP Candidate Images
Out-of-view images include images that are rendered below the fold or inside off-canvas menus, tabs, carousels, popups, modals, etc.
Lazy loading (a.k.a. on-demand loading) means that the content is only downloaded when it’s needed, e.g. when the user scrolls down the page and the images are about to come to the viewport. You can implement lazy loading in both HTML and JavaScript.
We have a full guide on lazy loading, but the easiest way is to add the loading="lazy"
attribute to the <img>
tag:
1 |
<img loading="lazy" src="image.webp" alt="Image" width="900" height="600"> |
The alternative value of the loading
attribute is eager
, which is the default value and means that the image will be loaded immediately.
Never lazy load LCP candidate images — doing so results in a Lighthouse warning and harms your Largest Contentful Paint results.
If you want to speed up your LCP candidate images, you can also consider preloading them by adding the following code to the <head>
section of your HTML page (note that you still need to add the preloaded image to the <body>
section using the <img>
tag):
1 |
<!-- Preload the LCP image in the head section -->
|
2 |
<head>
|
3 |
<link rel="preload" as="image" href="image.webp"> |
4 |
</head>
|
5 |
|
6 |
<!-- Render the preloaded image in the <body> section -->
|
7 |
<body>
|
8 |
<img src="image.webp" alt="LCP Candidate" width="900" height="600"> |
9 |
</body>
|
It’s typically worth preloading full-screen background images added by CSS or hero images that will likely be the LCP element on most viewports.
5. Use an Image CDN
You can further speed up your images and improve your Largest Contentful Paint results if you load your images (and possibly other static resources) from a Content Delivery Network (CDN).
A CDN is a globally distributed network of servers that reduces latency by loading images from a data center that’s geographically the closest to the respective visitor.
You can use a general-purpose CDN (such as Cloudflare, Akamai, Fastly, KeyCDN, etc.) that caches all static content and sometimes full pages to edge servers, or a dedicated image or media CDN (such as Imagify, Cloudinary, etc.) that also performs other image optimizations such as automatically generating responsive images and the belonging HTML code.
These days some hosting providers (e.g. some managed WordPress hosts such as Rocket.net or Kinsta) also integrate with Cloudflare or another CDN and automatically cache and load static content, including images, from CDN.
Prioritize Resource Loading with the fetchpriority
Attribute
The FetchPriority API, previously known as Priority Hints, implements a type of browser hint that allows you to give recommendations to the browser about how to prioritize resource loading on the page. As they are just hints, not commands, the browser can decide whether it wants to take them into consideration or not.
The fetchpriority
attribute is the HTML handle of the FetchPriority API.
You can add it to the <img>
, <iframe>
, <script>
, and <link>
elements to prioritize or deprioritize your images, iframes, and CSS and JavaScript files.
It takes one of the following values:
high
low
-
auto
(default value)
For example, you can add the fetchpriority="high"
hint to your LCP candidate images:
1 |
<img src="hero-image.webp" fetchpriority="high" alt="LCP candidate" |
2 |
width="900" height="600"> |
Interestingly, as the <link>
element also accepts the fetchpriority
attribute, you can add it to the resources you want to preload.
For example, you can make above-the-fold background images added by CSS preload faster:
1 |
<link rel="preload" as="image" href="css/images/background.webp" fetchpriority="high"> |
Wrapping Up
In this detailed guide, we looked into how to optimize Largest Contentful Paint using HTML and CSS.
You don’t necessarily have to use all of these techniques to get good LCP results; they’re more like recommendations that you can implement one by one, depending on your needs (e.g. on an image-heavy website, you may want to start with optimizing your images).
Frequent testing is also important as it can help you find the performance issues you need to work on. To test your site for Largest Contentful Paint, you can use a free tool such as Lighthouse, PageSpeed Insights (which also includes the latest field data from Google’s CrUX Report), or Google Search Console.
If you want to optimize Largest Contentful Paint to improve your SEO results, you need to pay special attention to the Chrome browser because the CrUX Report, used in Google’s search algorithm, only includes data from Chrome users (i.e. just Google Chrome, other Chromium-based browsers are not included in the CrUX data collection).
Nevertheless, it’s still important to avoid optimization techniques that harm user experience in other browsers (e.g. deferring non-critical CSS by loading the stylesheet before the closing </body>
tag delays page rendering in Safari — see the explanation above).
To learn more about Core Web Vitals in general, including the detailed calculation of the Largest Contentful Paint metric, check out my comprehensive guide to the initiative, too.