Sizing the LCP Image Right

Largest Contentful Paint (LCP) is one of the three Core Web Vitals metrics Google uses to assess user experience quality, measuring how quickly the browser renders the main content in the viewport. Having recently become a ranking factor, LCP remains unfamiliar territory for many developers — especially when the LCP element happens to be an image.

Letting the Browser Choose

The central idea behind optimizing an LCP image is to stop serving a single, fixed image file to every user. Instead, the browser should pick the most appropriate variant from a list, based on the device and context. This means users on smaller screens download smaller files, which improves loading speed and, in turn, the LCP score.

Two attributes on the <img> tag make this possible:

  • srcset — a list of candidate image files, each annotated with its intrinsic width
  • sizes — the intended display width of the image, optionally combined with CSS media queries

(The <picture> element offers an alternative when you also need format or viewport-based switching; the examples here stick with <img> for simplicity.)

The srcset Attribute

Use a mobile-first order when listing candidates: declare the smallest image first as the default via src, then repeat it in srcset followed by progressively larger variants. Each entry needs its intrinsic width expressed in pixels with the w unit:

<img
  src="image-300.png"
  
  alt="Image description"
/>

That example defines three files — image-300.png, image-900.png, and image-1800.png — of 300, 900, and 1800 px intrinsic width, with the 300 px version as the fallback defaults in both src and srcset. Always keep src, both to mark the default image and to support browsers that don't understand srcset. (The attribute can alternatively express image densities via Device Pixel Ratio, but the w-based approach is the focus here.)

A browser's choice logic needs to be understood before relying on it:

  • Selection is based on the viewport size. CSS styles and media queries have no influence; landscape orientation on mobile and tablet must be factored into the variant list.
  • Without a sizes attribute, the browser assumes the image will occupy 100% of the viewport width.
  • The device's Device Pixel Ratio (DPR) is folded into the calculation, so variants for each common DPR are needed for optimal loading.

The default selection formula is therefore:

viewportWidth x 100% x DPR

When no list entry matches exactly, the browser picks the closest; ties resolve in favor of the larger image.

An illustration of a woman next to a big folder with some images, and one of them is highlighted
(Large preview)

Consider a demo image displayed at a fixed 280 px width on all devices. On a 900 px viewport at DPR 1, the browser applies:

900px (viewport width) x 100% (view) x 1 (DPR)

It seeks a 900 px wide file — even though the display width is only 280 px. The image with an intrinsic width of 900 px gets downloaded anyway.

<img
  src="image-300.png" 
 
  alt="Image description"
/>

The practical takeaway: generate candidate widths with the formula imageRenderedSize x DPR. For a 280 px display and DPRs 1, 2 and 3, the variants should be:

  • DPR 1: 280 x 1 → 280 px wide
  • DPR 2: 280 x 2 → 560 px wide
  • DPR 3: 280 x 3 → 840 px wide
<img
  src="image-280.png" 
  alt="Image description"
/>

But that's still not optimal in mixed conditions. For a 1024 px laptop viewport at DPR 2, the ideal intrinsic width is 560 px, while the browser's default formula requests a 2048 px image — which is not even in the list. The sizes attribute fixes this gap.

The sizes Attribute

The sizes attribute overrides the "100% of viewport" assumption, letting the browser make a more accurate choice. When the image genuinely spans the full viewport width, the attribute is unnecessary.

Assign its value by one of these methods:

  • For responsive images (CSS width as a percentage), use a viewport-relative width such as sizes="50vw".
  • For fixed-display images, use a pixel value like sizes="500px".
  • To cover multiple viewports, chain media queries, e.g. sizes="(min-width: 1024px) 800px, (min-width: 768px) 80vw, 100vw"100vw being the fallback when no query matches.
<img
  src="image-280.png" 
 
  
  alt="Image description"
/>

(To express the image's viewport share as a percentage, compute (imageRenderedSize / viewportWidth) x 100, e.g. (280 / 320) x 100→ 87.5% of the viewport, which is 87.5vw.)

With sizes in place, the browser at any DPR can fetch precisely the right file — whether the display is 280 px across a 320 px viewport, or 480 px from a 768 px+ screen.

Proper image sizing alone does not finish the job. Real-world LCP optimization of the Resource load time subpart also calls for modern compression formats, long-lived cache headers, or shortening network distance with a CDN. And an image that is correctly integrated into <img> still won't perform if render-blocking scripts delay the element. Those concerns sit outside the scope of this article, but they matter once the image itself is well served.

Improving LCP Resource Load Delay

The <img> tag alone cannot address the time between the browser finding the LCP image and actually starting to fetch it — the Resource load delay. The browser needs to discover the resource first: HTML parsing must reach the <img> tag for its request to be dispatched. Several techniques can pull that discovery forward or otherwise relieve the pressure on the critical path.

Preload the LCP Image

When the LCP image is not immediately visible in the initial HTML — for instance, when it is inserted by a CSS background or is the first frame of a video — give the browser an explicit heads-up. A preload hint makes the request begin early rather than waiting for the resource to be encountered, effectively cutting the discovery delay and improving the LCP score.

The most reliable way in modern browsers is the image's own preload response header:

[[BLOCK]]

Ed. note: refer to the original as no preload snippet appears above the fold in the source part.

For same-origin URLs, the header benefits of preload work effectively. However, when response headers cannot be configured, an equivalent <link rel="preload" as="image" href="..."> element placed in the page head serves a similar role — albeit without the earliest timing advantages of the header. Preloaded image requests should carry fetchpriority="high" to elevate priority alongside the preload itself.

(A future-proofing note: HTML has a dedicated attribute — fetchpriority — which accepts high, low, or auto. Regrettably, current Chromium behavior is mixed with other hints such as importance, so defer to dedicated render-blocking resources and external scripts when a high-priority LCP image still battles other resources.)

Defer All Non-Critical Scripts

Every script requested before the LCP image begins to load will delay it if those scripts come first. By definition, user interaction scripts matter less than the main content: anything non-critical should be loaded with defer or async to move its request out of the pattern of blocking the fetch of content image.

Rather than sprinkling attributes into each <script> tag, in modern frameworks that bundle JavaScript as modules, the module's deferred-by-default semantics apply. Any non-module scripts on the critical path should be shifted with defer or async, whatever makes sense for their execution requirements.

Do Not Buffet the Image With CSS or Fonts

Be careful with layout-shifting CSS: if the LCP image is not reliably made visible by CSS as the stylesheet loads, the browser may recalculate the LCP candidate and fetch a different variant once styles apply.

Also, watch out for fonts or uncritical resources racing ahead of the image. The browser is made to prioritize images differently depending on a complex set of indicators, and a noisy head with many <link rel="stylesheet"> and <script> tags can push the image lower in priority — degrading the LCP even when the markup itself is theoretically optimal.

In summary: minimize time to the first byte of the image, move scripts off the critical path, and always keep an eye on what the browser actually fetches via the network panel. The image markup and size choices from the first half of this article only reward you fully when the request is uncovered and unblocked quickly.

Cutting LCP Load Delay On The Image Element

Once the image variants are properly declared, the next concern is when the browser actually gets around to fetching the LCP resource. A common culprit for delay here is lazy loading. Whether it is implemented via the native attribute or a JavaScript library, lazy loading adds time before the fetch begins — and that time is counted in the LCP metric. With a JS-based solution, the script itself must load before the image can be requested, creating an extra round trip that directly hurts the score.

Progressive rendering techniques such as Low-Quality Image Placeholders (LQIP) have the same problem. At the time of writing, Chrome does not consider an LQIP to be an LCP candidate, though the spec discussion is still open and this behavior could change.

Signaling Priority With fetchpriority

Browsers assign fetch priorities based largely on resource type, and images sit below render-blocking stylesheets and scripts. On top of a lower priority, the browser deliberately delays the initial fetch of low-priority resources so it can focus on the critical path. For a hero image, this default behavior needs to be overridden so the request goes out as soon as the HTML is parsed.

The fetchpriority="high" attribute shifts the image to a higher loading priority. It applies equally to an <img> inside a <picture> element. Browser support is currently strongest in Chromium, though Firefox was expected to ship support in early 2023. There is no downside to adding the attribute today; unsupported browsers simply ignore it.

<img fetchpriority="high" src="" alt="">

Adding the attribute to the markup is a one-line change, and the browser still picks the appropriate file from the srcset based on the current viewport:

<img fetchpriority="high"
  src="image-280.png"
 
 
  alt="Image description"
/>

When the <img> tag and its src/srcset attributes appear in the initial HTML, the preload scanner discovers the resource without needing an explicit <link rel="preload">. If you do choose to preload a responsive image, be aware that Safari does not support imagesrcset on the link element; in that case, omit the href attribute so non-supporting browsers do not request a useless fallback image.

An illustration of a man next to mobile with good LCP score on display highlighted in green
(Large preview)

Putting It Together

Optimizing the LCP image comes down to a few concrete steps:

  1. Generate the needed variants for every target device and DPR using the formula imageRenderedSize x DPR. Image transformation APIs can generate these versions automatically from a single high-quality source.
  2. Declare them with srcset on the <img> tag, following a mobile-first order and placing the default image in the src attribute.
  3. Set sizes correctly with media queries so the browser can pick the right file before layout completes. For responsive images, express the width as a percentage of the viewport with (imageRenderedSize / viewportWidth) x 100; for fixed-width images, use pixels.
  4. Raise the fetch priority with fetchpriority="high" and verify you are not lazy loading the LCP resource.