Web Performance

What lazy loading actually does to your largest contentful paint

Lazy loading is useful, but it is not a blanket performance fix. For LCP, it can help, hurt, or do nothing depending on which resource is delayed.

The Wux Webtools Team The Wux Webtools Team 8 min read AI-assisted, human-reviewed
Illustration of a web performance timeline with a highlighted image request affecting LCP
Table of contents
  1. Lazy loading is a scheduling decision, not a speed spell
  2. What the browser does when you lazy load an image
  3. The simple rule: never lazy load the LCP candidate
  4. Correction: use the actual internal URL
  5. When lazy loading can improve LCP
  6. The better pattern for LCP images
  7. Background images need extra care
  8. JavaScript lazy loading often makes things worse
  9. LCP is not always an image problem
  10. How to test lazy loading changes without fooling yourself
  11. A practical policy for most websites

Lazy loading is a scheduling decision, not a speed spell

Lazy loading is often described as a performance improvement, which is true in the same way that not packing a suitcase is a weight reduction. It helps because the browser does less work up front.

That distinction matters for Largest Contentful Paint, usually shortened to LCP. LCP measures when the largest meaningful element in the viewport is rendered. On many pages, that element is a hero image. On others, it is a large heading, a poster image, a product photo, or a content block.

Lazy loading changes when resources are requested. It does not make an image decode faster, a server respond faster, or a font render sooner. If you lazy load the wrong thing, especially the element that becomes LCP, you are telling the browser to wait before fetching the very thing it must display to pass Core Web Vitals.

That is why lazy loading is both overused and under-understood.

What the browser does when you lazy load an image

Native image lazy loading is usually added like this:

<img src='hero.jpg' loading='lazy' alt='...'>

With loading='lazy', the browser is allowed to defer fetching the image until it believes the image is likely to be needed. In practice, browsers use distance from the viewport, network conditions, image dimensions, and other heuristics. The exact rules are implementation details and can change.

With loading='eager', or with no lazy attribute in most cases, the browser treats the image as part of the normal loading process. It still has to prioritize among CSS, JavaScript, fonts, images, and other requests, but the image is discoverable immediately.

This means lazy loading primarily affects three phases:

  • Discovery: when the browser notices the resource.
  • Request start: when the network fetch begins.
  • Render timing: when the resource can finally be decoded and painted.

For LCP, the dangerous one is request start. If the LCP image request starts late, everything after it shifts late too.

The simple rule: never lazy load the LCP candidate

If an image is visible in the initial viewport and is likely to be the largest contentful element, do not lazy load it.

This includes:

  • hero images
  • primary product photos above the fold
  • large article lead images
  • large background-like images implemented as <img>
  • video poster images when the poster is the main visual element

The browser cannot render the LCP image until it has been requested, transferred, decoded, and painted. Lazy loading inserts uncertainty before the first step. Even a small delay can be enough to move LCP from acceptable to poor on a slower connection.

A common failure pattern looks like this:

  1. The server sends HTML.
  2. The browser parses an above-the-fold image.
  3. The image has loading='lazy'.
  4. The browser waits because the lazy-loading heuristic says it can.
  5. CSS and JavaScript continue to load.
  6. The image request starts later than it should.
  7. LCP is late, even though the image file itself is reasonably optimized.

This is frustrating because the page can look tidy in a code review. The problem is not file size alone. It is priority.

If you are reading lab output and trying to work out whether LCP is actually the issue, our guide to reading a Lighthouse report without panicking is intentionally practical: separate field data, lab hints, and fixes before you start changing code. (Note: if your routing is case-sensitive, use the exact URL from your CMS.)

Correction: use the actual internal URL

The correct Wux article URL is How to read a Lighthouse report without panicking. The point stands: identify the LCP element before changing loading behavior.

When lazy loading can improve LCP

Lazy loading can improve LCP indirectly when it keeps non-critical resources out of the browser’s way.

Imagine a product page with a hero product image at the top and a carousel of twelve recommendation images below the fold. If all thirteen images load eagerly, the browser may spend bandwidth and connection slots on images the user cannot yet see. On a constrained network, that can compete with the hero image, CSS, or font files.

Lazy loading the below-fold carousel images can help the LCP image load earlier because fewer non-critical requests compete during the initial page load.

This is the legitimate performance case for lazy loading:

  • eager load the above-the-fold LCP candidate
  • lazy load images below the initial viewport
  • avoid heavy scripts that inject important images late
  • keep image dimensions in the HTML to avoid layout shifts

Lazy loading is not an LCP optimization by itself. It is a resource prioritization tool. It helps when it protects the critical path.

The better pattern for LCP images

For an above-the-fold LCP image, the goal is to make the browser discover it early, request it early, and render it without layout instability.

A solid baseline looks like this:

<img
  src='/images/product-hero.avif'
  srcset='/images/product-hero-800.avif 800w, /images/product-hero-1400.avif 1400w'
  sizes='(max-width: 768px) 100vw, 720px'
  width='1400'
  height='900'
  loading='eager'
  fetchpriority='high'
  decoding='async'
  alt='Black hiking backpack with roll-top closure'
>

The important parts are not decorative:

  • loading='eager' prevents lazy-loading delay.
  • fetchpriority='high' tells the browser this image matters.
  • width and height reserve space and reduce layout shift.
  • srcset and sizes prevent oversized downloads.
  • A modern format can reduce transfer time when used carefully.

If you are still serving one large JPEG to every screen, image format and responsive sizing may matter more than the lazy-loading attribute. For a practical decision tree, see when AVIF beats WebP and when it does not.

Background images need extra care

CSS background images are not discovered as early as normal HTML images. The browser must fetch and parse CSS before it knows about them. If your LCP element is a CSS background image, you have already made discovery harder.

That does not mean background images are forbidden. It does mean you should be deliberate.

For decorative images, CSS backgrounds are fine. For meaningful hero imagery, an <img> or <picture> element is usually better because it is visible to the HTML parser, supports alt text, and works well with responsive image attributes.

If you must use a CSS background for an LCP image, consider preloading it:

<link rel='preload' as='image' href='/images/hero.avif'>

Preload is not a magic wand either. Preloading too many images creates the same priority problem in a different costume. Use it for the one image that really matters, not for every image in the design system.

JavaScript lazy loading often makes things worse

Before native lazy loading was widely supported, many sites used JavaScript libraries that swapped data-src into src after page load or after an intersection observer fired. Some still do.

This can be reasonable for long article pages or image-heavy galleries. It is a poor choice for above-the-fold content.

The browser preload scanner is fast, but it cannot request an image whose URL is hidden in a custom attribute until JavaScript runs. If your hero image starts as data-src='hero.jpg', you have delayed discovery behind script download, parsing, execution, and framework hydration.

That is a bad trade for LCP. Put critical image URLs in real HTML. Let the browser do its job.

LCP is not always an image problem

On some pages, the LCP element is text. In that case, lazy loading images may have little direct effect. Your bottleneck may be render-blocking CSS, slow server response, client-side rendering, or web fonts.

Fonts are worth calling out because they are a frequent hidden cause of late text rendering. A large heading can become LCP, and font loading behavior can delay or alter when that heading paints. If your image work is not moving the metric, inspect the LCP element directly rather than assuming. Our article on web fonts as a performance win covers the boring fixes that often work: fewer weights, modern formats, sensible fallbacks.

How to test lazy loading changes without fooling yourself

Do not test by staring at your page on office Wi-Fi. You need to see request timing.

Use this workflow:

  1. Open Chrome DevTools and record a Performance trace.
  2. Enable network throttling, such as Fast 4G or Slow 4G.
  3. Reload the page with cache disabled.
  4. Find the LCP marker.
  5. Identify the LCP element.
  6. In the Network panel, check when that resource started loading.

If the LCP resource starts late, ask why:

  • Was it lazy loaded?
  • Was it injected by JavaScript?
  • Was it hidden in CSS?
  • Was it deprioritized behind other images?
  • Was the server slow to respond?

Then make one change and retest. Performance work gets messy when teams change image format, lazy loading, preloading, JavaScript bundles, and CDN settings in the same deployment. You may improve the page, but you will not know which change mattered.

Field data matters too. Lab tools are useful for diagnosis, but LCP varies by device, network, viewport, cache state, and geography. Use real-user monitoring or Chrome User Experience Report data when you can.

<!-- tool-cta:start -->

💡 Try this: Keep your LCP image small and eager-loaded by running it through the Image Compressor so it paints quickly without needing lazy loading.

<!-- tool-cta:end -->

A practical policy for most websites

For most marketing sites, ecommerce pages, documentation sites, and publisher pages, this policy is enough:

  • Above-the-fold primary image: eager load, consider high fetch priority.
  • Below-the-fold content images: lazy load.
  • Icons and tiny UI assets: usually not worth thinking about individually.
  • CSS background hero: reconsider as HTML image or preload carefully.
  • JavaScript-injected hero image: fix the rendering architecture if possible.
  • Carousels: eager load the first visible slide only; lazy load the rest.

There are edge cases. Browser heuristics improve. Frameworks add automatic image components. Some platforms now avoid lazy loading images detected near the viewport. Still, the principle does not change: critical resources should be early and obvious; non-critical resources should wait.

Lazy loading is valuable when it expresses that distinction. It is harmful when it hides the most important content from the browser until after the page has already started losing the LCP race.

Frequently asked questions

Should I ever use loading='lazy' on a hero image?
Almost never. If the hero image is visible in the initial viewport, it is likely to affect LCP and should be loaded eagerly.
Does lazy loading improve Core Web Vitals?
It can, but indirectly. Lazy loading below-the-fold images may reduce early network competition and help LCP. Lazy loading the LCP image usually makes LCP worse.
Is fetchpriority='high' a replacement for eager loading?
No. Use it as an additional hint for important images. The browser still needs to discover the resource early, and the image should not be hidden behind lazy loading or JavaScript.
What if my LCP element is text, not an image?
Then lazy loading images may not change LCP much. Look at server response time, render-blocking CSS, client-side rendering, and web font behavior.
Should every below-fold image be lazy loaded?
Usually yes, especially on long pages. The exceptions are images that are likely to enter the viewport immediately or are needed for layout-critical interactions.

Sources & further reading

  1. web.dev: Browser-level image lazy loading for the web
  2. web.dev: Optimize Largest Contentful Paint
  3. MDN Web Docs: Lazy loading
  4. Chrome Developers: Optimize resource loading with the Fetch Priority API
About the author
The Wux Webtools Team

Last updated:

Keep reading