Text reads Fix your website's Largest Contentful Paint.

Fix your website's Largest Contentful Paint by optimizing image loading

Author avatarDebugBear10 minute read

Images make up a big part of most websites. They're also often the largest element that first appears on your page, and thus the Largest Contentful Paint (LCP) element - a metric from Google's Core Web Vitals. When images load slowly, they can make your LCP time longer. This hurts your Core Web Vitals scores and overall user experience. Let's look at how to make images on the web load faster.

Introduction

When you add an image to your website, there's more happening behind the scenes than you might think. Starting with the basics, the simplest way to add an image onto a page is with a HTML <img> element:

html
<img src="image.jpg" alt="Description of the image" />

You can also add images with CSS background images, which are still candidates for LCP:

css
.container {
  background-image: url("image.jpg");
}

Some developers load images using JavaScript instead. This delays when the browser starts downloading the image, since JavaScript has to run first.

js
// Don't load images like this unless you have a good reason
const img = new Image();
img.src = "image.jpg";
document.querySelector(".container").appendChild(img);

In fact, the JavaScript approach can be far worse when you have JavaScript files requesting other JavaScript files, which then request images. This is known as a request chain, and is explained later in this article.

To understand image loading, we use network request waterfalls. These show the timing of all network requests on your page. Modern performance testing tools like DebugBear can generate these waterfalls for you, and can even identify the LCP element on your page, as shown by the LCP badge in the previous screenshot.

How images load on a webpage

What happens when an image loads on a web page? Understanding this process helps explain why certain optimization techniques work.

When the browser finds an image in HTML that it's loading, the browser:

  1. Makes a network request to download the image: This is just like downloading any other resource, like CSS or JavaScript.
  2. Assigns a priority to that request (more on this later): This determines when the image downloads compared to other resources.
  3. Decodes the image data once it arrives: This is the process of turning the raw image data into pixels that can be displayed on the screen.
  4. Renders the image on the page: The browser positions and displays the image according to the page layout.

Unlike some resources (such as CSS), image downloads don't block the rest of the page from loading.

How image loading affects LCP

LCP is made of sub-parts. Each sub-part has an impact on the overall LCP score. The slower the sub-parts, the slower the LCP score. The following screenshot shows a part of DebugBear's Real User Monitoring dashboard that has identified LCP sub-parts for a real page view.

Screenshot from DebugBear's Real User Monitoring dashboard showing LCP sub-parts

Knowing each sub-part is key to understanding how to improve your LCP score, so let's have a look at the sub-parts in detail.

Time to First Byte (TTFB)

This is the time it takes for the server to start responding with the main HTML document. This can be notably slow if:

  • There's a large geographic distance between the server and the user.
  • There are incorrect cache headers or the website uses an unoptimized cache strategy.

This screenshot shows network timing information for a particular request. It's interesting that the TTFB took longer than the download itself! In fact, the other resources which are highlighted also have a longer TTFB than the download itself!

A tooltip in a network request waterfall that shows a long TTFB relative to the download time

Resource load delay

This is the time it takes the browser to discover the image and request the image. This can be slow if:

  • The image is in a request chain, where resources have to load before the image can start downloading.
  • The image is lazy loaded, which delays the download until the image is near or in the viewport.
  • The image is inserted into the DOM using JavaScript - even if that JavaScript is inlined in the HTML. This is because the browser's look-ahead parser has to parse and execute the JavaScript before the image can be discovered.

Resource download time

This is the time it takes for the image to download, and is likely what most of us think of when we think of a "slow loading image". This can be slow if:

  • The image is large and not optimized.
  • Off-screen images, or other non-critical resources are downloading at the same time, causing network contention.

The following screenshot shows a collection of JavaScript files where the download time far outweighs the TTFB. Request waterfalls within DebugBear indicate where actual content download is happening within the horizontal blue bars. The darker shades of blue indicate where content download is in progress.

Request waterfall showing large download times for resources.

Resource render delay

This is the time it takes for the browser to decode the image data and render it on the page. This can be slow if:

  • The image uses a highly compressed format which takes more time to decode.
  • Render-blocking resources are still downloading, which can delay the image from rendering.
  • The main thread is blocked, and cannot render the image to the page.
  • The image was loaded using a <link rel="preload"> element, but the page element using the image hasn't been created yet

Modern image formats and responsive images

Modern image formats help reduce image file sizes while maintaining quality. Using a modern image format can have a positive impact on the Resource download time sub-part of LCP. The recommended modern formats are:

  • WebP: Widely supported and offers good compression.
  • AVIF: Newer format with even better compression.

You can use the Squoosh web app to convert your images to these formats. Here's how to serve different formats for browsers to choose from:

html
<picture>
  <source srcset="image.avif" type="image/avif" />
  <source srcset="image.webp" type="image/webp" />
  <img
    src="image.jpg"
    alt="Description of the image"
    width="1200"
    height="800" />
</picture>

The following screenshot shows an LCP resource from a request waterfall. While TTFB is significant, it doesn't compare to a whole 3 seconds of content download time for the image. Considering the lab test was run using a throttled connection, and considering this image is 1MB in size, this begins to make sense.

Large content download for an LCP image resource

Putting the 1MB image into Squoosh, and converting it to AVIF @ 80% quality, gives a 95% saving which results in a 46KB file. This has a positive impact on content download time.

Squoosh converting a large image into AVIF

It's important to think about browser support when using modern image formats. You could also consider configuring your server to serve the right format based on the browser's Accept header.

Responsive images help you serve the right size for each screen. This saves bandwidth and processing time.

html
<img
  src="small.jpg"
  srcset="small.jpg 300w, medium.jpg 600w, large.jpg 900w"
  sizes="(max-width: 500px) 300px,
         (max-width: 900px) 600px,
         900px"
  alt="Description of the image" />

The example uses the <picture> element to specify what image is available for various dimensions:

  • A small image on small screens (less than 500px wide).
  • A medium image on medium screens (500px to 900px wide).
  • A large image on large screens (over 900px wide).

This way, you don't waste bandwidth for large images on small screens. As a practical example, this website takes a large amount of time to download an LCP image of 2.26MB, even on a mobile viewport.

Request waterfall showing a large LCP image

Upon further investigation, it turns out the image has dimensions of 1920 × 1080. After optimizing this image to use a modern image format, the developers working on this website can produce different versions of the image suitable for different viewport dimensions.

How request chains impact LCP

Sometimes images appear later than they should because of request chains. A request chain happens when one or more resources (outside of the main HTML) have to load before another request can start:

Request chain in a network request waterfall

For example, if your JavaScript loads an image, at a high level, the browser has to:

  1. Download the HTML.
  2. Download the JavaScript: Depending on how the JavaScript is loaded, this can also block page rendering.
  3. Execute the JavaScript: There is a parse and execution cost for JavaScript, which is often exacerbated on low-end devices.
  4. Start downloading the image.

This creates a chain that delays the image load. You can help break this chain by:

  • Using a regular <img> element directly in the HTML.
  • Using the <link rel="preload"> element to download the image earlier.

The preload hint tells the browser to download a resource as soon as possible, and is placed in the head of your HTML:

html
<link rel="preload" as="image" href="image.jpg" />

In this example, DebugBear identifies that the LCP image is part of a chain, and also offers an experiment to test a fix.

A request chain example

The experiment automatically preloads the request chain for the LCP image, including the LCP image itself, into the main HTML document. With the experiment run, a before and after comparison is available to view:

The results of a DebugBear experiment

The previous screenshot shows:

  • The CSS resource part of the LCP request chain is now preloaded.
  • The LCP image itself is now preloaded.

The before and after LCP score below confirms that an improvement was made.

LCP score improvement

Using fetchpriority=high on key images

Browsers assign different priorities to network requests. This determines the order resources download when they compete for bandwidth. You can increase the priority of your LCP image with the fetchpriority attribute:

html
<img src="image.jpg" alt="Description of the image" fetchpriority="high" />

This tells the browser to prioritize downloading the image, relative to other resources. You can also use fetchpriority with the rel="preload" attribute:

html
<link
  rel="preload"
  href="/background-image.avif"
  as="image"
  fetchpriority="high" />

This can be especially useful when the LCP is a background image, and would otherwise be discovered late in the page load - after the CSS has been downloaded and parsed.

This example shows an improvement on Discord, where the entire request chain for the LCP image is preloaded, and marked with fetchpriority="high", which helps improve the LCP time.

Score difference:

LCP score difference on Discord

Request waterfall difference:

Request waterfall diff view showing the LCP image is preloaded

The previous screenshot shows how Discord's LCP image was downloaded much earlier.

The FP=HIGH badge confirms that a high fetchpriority was specified, so the LCP resource now maintains a high priority, whereas previously, the LCP image started out as a low priority.

A red vertical line in a request waterfall. The red line shows where a priority change occurred.

The thin red vertical line appears in the DebugBear request waterfall view for a resource which has had its priority changed. If the browser is late to discover that a priority change is needed (for example from a low priority to a high priority), you can assist the browser by applying fetchpriority="high" on the applicable resource.

Lazy loading images

Not every image needs to load right away. Images below the viewport can load later, saving bandwidth for more important resources. Here's how to lazy load an image:

html
<img loading="lazy" src="image.jpg" alt="Description of the image" />

Consider a web page with 10 images, where only the first image (the LCP element) is visible in the initial viewport. In such a scenario, lazy loading the remaining 9 images can help improve loading of the first image. When you lazy load images below the fold, you:

  1. Reduce competition for bandwidth. When you free up bandwidth, the LCP image can sometimes load faster.
  2. Let the browser focus on loading important resources first.
  3. Save user's data if they never scroll down.

This example shows two different waterfalls for the same website, these examples attempt to show the impact that resource competition can have on an LCP image.

Waterfall 1 shows a large LCP image that takes a long time to download, and appears to be competing for bandwidth with the other image resources on the page.

An LCP resource download experiencing network contention

Waterfall 2 shows the same large LCP image, but this time, DebugBear's request blocking is used to block other image resources that are not the main LCP image. This is done as a test, to show how bandwidth competition impacts resource download time.

An LCP resource download that has bandwidth availability

If you look closely, the actual data download (the dark blue patches) for the RB_8220-Large-1.png image are a lot more concentrated in the second waterfall. To understand the significant impact this has on timing, take a look at the waterfall comparison view. For the test run where the LCP image didn't have to compete for bandwidth, the image downloaded in less than half the time!

Waterfall comparison view for vitol.com

Summary

Images are a big part of most websites, and can have a big impact on your LCP score. Here's a summary of how to optimize images for LCP:

  1. Use modern image formats like WebP and AVIF.
  2. Serve responsive images to save bandwidth.
  3. Use preload and fetchpriority to prioritize key images.
  4. Lazy load images below the fold to reduce network contention.
  5. Avoid request chains that delay image loading.

This post is sponsored by DebugBear. DebugBear helps teams optimize web performance and Core Web Vitals, providing speed insights and continuous monitoring for faster websites.

Stay Informed with MDN

Get the MDN newsletter and never miss an update on the latest web development trends, tips, and best practices.