Back to Article index

Improving Image CLS and LCP at the Source

Practical techniques for preventing image-related layout shifts, improving LCP, and reducing transfer size.

Published
April 20, 2023
Read in Japanese

An img declared as width="400" height="200" whose real dimensions are 400x300 pushes everything below it down by 100px the moment it finishes loading.

Images are heavy, and they tend to sit near the top of a page. While using Lighthouse to improve a product, I collected several lessons about handling them. The examples use Tailwind CSS; the underlying CSS works anywhere.

Prevent layout shifts

Images arrive over the network and may appear after the surrounding text. If the browser cannot reserve space in advance, the content below the image moves when the file finishes loading. A button shifting just as someone tries to click it is a frustrating example of this behavior.

This unexpected movement is a layout shift. Reserving the image’s space from the start reduces Cumulative Layout Shift (CLS), improving both Core Web Vitals and the experience of using the page.

When the dimensions are known

When you know an image’s dimensions, provide accurate width and height attributes on the img element. Use CSS to control its responsive display size. The browser can then:

  1. Calculate an aspect ratio from the width and height attributes.
  2. Combine that ratio with the CSS width or height to reserve the required area.
  3. Draw the image in that area once the resource is available.

In the following example, the source is 400px wide and 300px high, so the reserved height is three quarters of the rendered width. Even though CSS makes the image as wide as its parent, the browser can still calculate the height before the image loads.

<img
  src="https://picsum.photos/400/300"
  width="400"
  height="300"
  class="w-full h-auto" />

If the attributes describe the wrong ratio, the shift from the opening paragraph is what you get once the browser learns the resource’s intrinsic dimensions:

<img
  src="https://picsum.photos/400/300"
  width="400"
  height="200"
  class="w-100 h-auto" />

At a rendered width of 400px, the declared 2:1 ratio initially reserves 200px of height. The image itself has a 4:3 ratio and ultimately needs 300px, producing a 100px shift. If the dimensions are known, make the attributes accurate.

When the dimensions are unknown

Sometimes the dimensions are not available in advance, as with user uploads, API-provided images, third-party embeds, or advertisements. In these cases, CSS aspect-ratio can reserve a predictable box before the content is ready.

The next example creates a 16:9 box. At 400px wide, it reserves 225px of height. Use object-fit: cover to fill the box while preserving the image’s ratio, or contain to keep the whole image visible inside it.

<img
  src="https://picsum.photos/400/300"
  class="w-100 h-auto aspect-video object-cover" />

aspect-ratio works for elements other than images, so the same technique can reserve space for an iframe or another embed.

Improve LCP

Largest Contentful Paint (LCP) measures when the page’s main visible content finishes rendering. When the LCP element is an image, avoid delaying its request and make the resource discoverable from the initial HTML whenever possible.

Give the important image the right priority

Adding fetchpriority="high" to the LCP image gives the browser a hint that the request is important. It is a hint rather than a command, and marking too many resources as high priority makes them compete with one another.

For a resource the HTML parser cannot discover directly, such as an important CSS background image, preloading can expose it earlier:

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

In Next.js 16 and later, the Image component provides a preload prop. The older priority prop is deprecated.

Never add loading="lazy" to the LCP image. Lazy loading postpones the request until layout confirms that the image is near the viewport, creating an unnecessary resource-load delay.

Make the LCP resource discoverable in HTML

When an image URL is introduced only by CSS or JavaScript, the browser must download and process that file before it can discover the image. Prefer an img with src or srcset in the HTML. When that is not possible, consider a preload for the hidden resource.

Reduce transfer size

Smaller image responses take less time and bandwidth to transfer. Use picture, srcset, and sizes to offer images appropriate for the device and rendered layout.

Choose and compress the format

Do not select one image format for every asset. Choose among AVIF, WebP, JPEG, PNG, and other formats based on the content and delivery environment. Tools such as Squoosh help compare quality and size, and serving an image close to its rendered dimensions prevents wasted bytes.

Next.js Image or an image CDN can generate variants for the browser’s supported formats and the requested display size. Measure the resulting quality and transfer size rather than optimizing for the file extension alone.

Image work does not end with one extra attribute. Decide which image carries the page, serve it at a sensible size and format, and order the loading around that decision.

References