A Developer’s Guide to Improving Largest Contentful Paint(LCP) for better web performance

In recent years, Web Performance has become a critical topic, not only for developers but also for SEO specialists and marketers. Companies are increasingly investing in improving web performance, as it directly impacts Google’s ranking algorithm and, by extension, the visibility of their business online. A pivotal moment occurred in 2021 when Core Web Vitals were incorporated as ranking factors in Google’s search algorithm.

Short introduction to Google Web Core Vitals

Web Core Vitals are a set of standardised metrics introduced by Google to measure and improve the quality of a website’s user experience. These metrics focus on key aspects of web performance, ensuring sites load quickly, remain visually stable, and are responsive to user interactions. The primary metrics include:

  1. Largest Contentful Paint (LCP): Measures how fast your website visibly loads, targeting the time it takes for the largest visible content (e.g., an image, video and/or block of text) to load. LCP should not exceed 4s, ideally it’s below 2.5s.
  2. Cumulative Layout Shift (CLS): Measures how smooth and predictably elements load into the page. For example, having the content being pushed down after an image is loaded will impact this score negatively.
  3. Interaction to Next Paint (INP): Evaluates responsiveness, measuring how quickly a page reacts to user inputs like clicks or taps. INP should not exceed 500ms, ideally it’s below 200ms.

​The easiest way to get these metrics for your website is via the the Lighthouse tool in Chrome or running a test in Google PageSpeed Insights. You can see an example of DevRecipes’s results below:

Google pagespeed insight results for devrecipes.net

Largest Contentful Paint (LCP) is often the most challenging performance metric for websites and has a significant impact on user experience. LCP typically involves large, visible elements like images, videos, or text blocks that are critical to the initial impression of a page. For many websites, LCP issues are more common on mobile devices than on desktop, due to network constraints and resource-heavy elements.

To optimize LCP, it’s essential to first identify the largest visible element users see upon landing on the page – often the header, page cover, or a prominent heading. Tools like Google Lighthouse and PageSpeed Insights can provide detailed LCP reports to pinpoint areas for improvement. Below are actionable strategies to enhance your LCP score effectively.

Image Optimization

According to the HTTP Archive, images are the most-requested asset type for most websites, and they usually take up more bandwidth than any other resources like CSS, JS and Fonts. Using heavy images can significantly harm your LCP score, even if other optimizations are in place, therefore we will start with image optimizations:

Image size

Most websites don’t require a 2MB image for a cover or hero section. Ideally, hero or background images should not exceed 1920px in width for desktop use. Tools like Squooosh, a free image optimization tool by Google, make it easy to resize and compress your images without sacrificing quality. By keeping images lightweight and appropriately sized, you can drastically improve LCP and enhance user experience.

Squoosh can help you reduce the image size significantly


Use modern image formats like WebP and AVIF.

WebP images are about 30% smaller in size compared to PNG and JPEG images at equivalent quality. WebP has a good browser support and you can easily convert your image with Squooosh.

WebP images are approximately 30% smaller than PNG and JPEG formats while maintaining similar quality, making them a great choice for optimizing web performance. With strong browser support, WebP is widely compatible and easy to adopt. Squooosh can help you with coverting your images to WebP but there are various tools available online.

Use responsive images

Most modern browsers now support the srcset attribute for images, allowing developers to specify the dimensions of each image version in advance. This helps the browser choose the most suitable image based on the viewport size, significantly improving loading times and metrics like LCP. Remember, when using the srcset attribute, specify dimensions in w units (e.g., 320w), not pixels (320px). This ensures proper implementation and better performance for responsive designs.

<img
  src="default-small-image.jpg"
  srcset="default-small-image 320w, medium-image.jpg 800w, large-image.jpg 1200w"
  alt="Image description"
/>

Optionally, you can use the sizes attribute to define how much screen space an image will occupy at specific viewport widths. This approach effectively creates a “placeholder slot” for the image, ensuring it integrates seamlessly into the layout and significantly reduces layout shifts when the image is loaded. This technique is particularly helpful in improving visual stability and metrics like CLS (Cumulative Layout Shift) :

<img
  srcset="small-image.jpg 480w, large-image.jpg 800w"
  sizes="(max-width: 600px) 480px, 800px"
  src="large-image.jpg"
/>

You can take your image optimization a step further by using the <picture> element, which enables more advanced selection criteria using media queries. This approach lets you serve different images based on conditions like screen orientation or resolution, while also providing a fallback image. This level of control helps tailor the user experience to specific devices and layouts. Here’s an example:

<picture>
  <source media="(orientation: landscape) and (min-width: 800)" srcset="image-medium.png 800w, image-large.png 1200w" />
  <source media="(orientation: portrait)" srcset="image-small-portrait.png 320w, image-medium-portrait.png 800w" />
  <!-- Default image --> 
  <img src="image-small.png" />
</picture>

If you’re using WebP or AVIF, you can also use the picture element to set a fallback image if the browser doesn’t support the new formats:

<picture>
  <source type="image/webp" srcset="image.webp">
  <img src="image.png">
</picture>

If you use background-image within CSS media queries, be aware that the browser will typically load all specified images regardless of which media query applies. This behavior limits optimization opportunities and can lead to unnecessary data consumption and slower performance. To address this:

  • If you must use a background-image, consider manually preloading the image or optimizing its delivery to reduce loading times.
  • Use <img> or <picture> with srcset instead, as these allow for more efficient image selection and loading.

You can prioritize loading important resources like the LCP element, such as a background image, by using preload. This instructs the browser to fetch these critical resources early in the page load process, reducing delays.

For example, if your LCP element is a background image, you can preload it in the <head> section of your HTML like this:

<link rel="preload" href="background-image.webp" as="image"/>

Open the Network tab and you will see for yourself that the image is now among the first elements to be loaded. The same can be done with other types of resources as well – Fonts, CSS, JavaScript:

<link rel="preload" as="script" href="critical-script.js">
<!-- Make sure to add "crossorigin" for fonts  -->
<link rel="preload" href="SomeFont.woff2" as="font" type="font/woff2" crossorigin>

Use the “loading” attribute for offscreen images

You can use the loading attribute in the <img> tag to control the loading behavior of images, especially for optimizing performance. This attribute has two possible values:

  1. lazy: This defers the loading of offscreen images until they are close to entering the viewport. It’s a great way to improve page load speed by only loading images when needed, which can be particularly useful for long pages with many images.
  2. eager: This is the default behavior, where the browser loads the image as soon as possible, regardless of whether it is in the viewport.

Use loading="lazy"only for images for which you’re certain that are not LCP element and won’t be in viewport when the page loads.

<img src="image.png" loading="lazy" width="200" height="200">

Use “fetchpriority” attribute

Use the fetchpriority HTML attribute to specify a download priority to resources like images, CSS, fonts, and scripts when they are loaded via <link>, <img>, or <script> tags. This attribute accepts three values: high, low, and auto. For instance, you can apply fetchpriority="low" to images that aren’t critical to the initial page load, allowing the browser to prioritize more important resources first.

<script src="very_important_script.js" fetchpriority="high"></script>
<img src="img/cover.jpg" fetchpriority="high">

<-- In the same way you can deprioritize unimportant resources:
<script src="unimportant_script.js" fetchpriority="low"></script>

You can use use fetchpriority for fetch requests too:

const criticalContent = await fetch('/api/users', {priority: 'high'});

Avoid loading fonts via CSS @import

There are two main ways to load fonts: either using the <link> tag in HTML or by employing @import or @font-face in CSS. Whenever possible, it’s best to use the <link> tag in HTML, as it offers better control over the font loading behavior. CSS methods, especially @import, can introduce render-blocking behavior because the browser waits for all imported resources to be downloaded before it starts rendering the page. This delay can significantly slow down your website and negatively impact your LCP score. If you’re using a Google Font, here’s the recommended approach for importing it:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300&display=swap" rel="stylesheet">

Use “defer” for non-critical JavaScript

By default, when the browser encounters a JavaScript file, it starts downloading and executing it immediately, which blocks the parsing and rendering of the page (main execution). This results in a delayed user experience. Instead, you can use the defer attribute, which tells the browser to delay the execution of JavaScript until after the page content has been rendered but before the DOMContentLoaded event is fired. This ensures that the JavaScript file doesn’t block the page from loading and allows for faster rendering of the page’s content.

How script defer works
<script defer src="script.js">

Another attribute which you should know about is async , but it’s render-blocking, so it’s used very rarely.

Use “preconnect” to speed-up the connections to another origin

The preconnect keyword in the rel attribute of the <link> element signals to browsers that resources from a specified origin are likely to be needed soon. By initiating an early connection to the target origin, the browser can potentially enhance user experience by reducing the delay caused by the handshake process during resource fetching. Preconnecting helps accelerate subsequent interactions by preparing parts of the connection in advance(DNS+TCP+TLS handshake for HTTPS origins)

Use CDN for static assets

CDNs distribute your website’s assets (e.g., images, videos, CSS, JavaScript) across multiple servers worldwide. This ensures that users fetch resources from a server geographically closer to them, reducing latency and improving loading times for large LCP elements like hero images or videos.
CDN’s can also compress text-based resources (like CSS and JavaScript) to further speed up the loading time.