PerformanceApril 29, 20268 min read

Lazy Loading Images in Ecommerce: When It Helps, When It Hurts, and How to Get It Right

Lazy-loaded images can save 40% on page weight or destroy your LCP score depending on implementation. Here's the rule for product pages and a step-by-step setup that actually works.

StoreVitals Team

Lazy loading sounds like a free performance win — load only the images the visitor actually scrolls to. In practice, it's one of the most commonly mis-implemented techniques in ecommerce, and a wrong setup can hurt your Core Web Vitals more than no lazy loading at all.

This guide explains when lazy loading helps, when it hurts, and the exact setup that works on product pages, category pages, and homepages.

What Lazy Loading Actually Does

By default, browsers download every image referenced in HTML as soon as they parse the tag — even images far below the fold. On a product detail page with 15 alternate angles and a "frequently bought together" carousel, that's 15-30 image requests competing for bandwidth before the first viewport renders.

Lazy loading defers off-screen images until the visitor scrolls toward them. The browser knows because of loading="lazy" on the <img> tag (native lazy loading) or because JavaScript intercepts and loads on scroll (manual lazy loading).

Where Lazy Loading Helps

  • Long category pages: 50+ products on a single page. Only 6-8 visible above fold. Lazy loading saves 80% of image bytes for users who don't scroll past row 2.
  • Product detail with many alternate views: Hero image visible, 9 thumbnails below or in a swiper. Lazy load everything except hero.
  • Blog posts with embedded images: Article-length pages with 5-10 inline images.
  • Footer images: Logos, payment badges, social icons. Always lazy.

Where Lazy Loading Hurts

This is where teams routinely break their LCP score:

1. The hero / LCP image

Largest Contentful Paint is the time to render the largest image visible in the viewport on first paint. If you lazy load the hero, the browser waits to start fetching it until JavaScript runs or the intersection observer fires. That delay shows up directly as worse LCP.

Rule: The hero image and any image above the fold should never be lazy loaded. If anything, mark them fetchpriority="high" so the browser prioritizes them over below-fold content.

2. Product page main image on mobile

On desktop, the gallery thumbnails are next to the hero. On mobile, they're often below — so the hero IS above the fold and dominates first paint. If your template uses the same component for both and lazy loads everything, mobile LCP is the casualty.

3. Above-the-fold logos and badges

Trust badges in the header, payment icons in checkout, store logo. These are tiny but still LCP-eligible if larger than other elements. Don't lazy load them.

4. Images that are visible but in a hidden container

A common bug: a slider where slide 1 is visible but slides 2-10 are technically in the DOM. If the slider script loads slide 2's image only when the user advances, but you marked all images loading="lazy", slide 2 might not start loading until the user scrolls. The slider feels broken.

The Right Implementation

Native lazy loading (preferred)

Modern browsers all support loading="lazy". No JavaScript needed.

<img src="hero.webp" alt="Hero image" fetchpriority="high">
<img src="thumb-1.webp" alt="Thumbnail 1" loading="lazy">
<img src="thumb-2.webp" alt="Thumbnail 2" loading="lazy">
<img src="related-1.webp" alt="Related" loading="lazy">

The first image is the LCP candidate — high priority, no lazy. Everything below the fold gets loading="lazy". Done.

Add explicit width and height

Without intrinsic dimensions, lazy-loaded images cause layout shift when they finally load (CLS hit). Always specify width and height attributes — even if your CSS overrides them, the browser uses them to reserve space.

<img src="thumb.webp" alt="Product" loading="lazy" width="800" height="800">

Combine with responsive images

Lazy loading works with srcset — the browser still picks the right size, just defers the load.

<img
  src="product-800.webp"
  srcset="product-400.webp 400w, product-800.webp 800w, product-1200.webp 1200w"
  sizes="(max-width: 768px) 100vw, 800px"
  alt="Product"
  loading="lazy"
  width="800"
  height="800"
>

The Common Mistakes

Marking everything lazy

Themes that "support lazy loading" often add loading="lazy" to every image. This destroys LCP. Audit your theme — there should be at least one image per page with fetchpriority="high" and no lazy attribute.

Using a JavaScript lazy loader on top of native

If you use both (native loading="lazy" AND a JS library that listens for scroll events), the JS library often holds the image in data-src and replaces with src on scroll. The browser's native lazy loading sees the placeholder, doesn't see the real image, and the script controls everything. This adds complexity without benefit. Pick one.

Lazy loading background images via CSS

CSS background images don't have a lazy attribute. If you must use background images, the only way to lazy load them is with JavaScript (intersection observer). For ecommerce, prefer <img> elements over CSS backgrounds — easier to optimize, accessible, indexable in image search.

Forgetting noscript fallbacks

If you use a JavaScript lazy loader (not native), include a <noscript> tag with the real image. Otherwise users with JS disabled and search engine crawlers may not see images at all.

Validating Your Setup

After implementing:

  1. Run Lighthouse on your top 3 page templates. LCP should be under 2.5s on mobile.
  2. Check the LCP element in Lighthouse — make sure it's the image you intended (usually the hero).
  3. Disable JavaScript and reload — images should still appear (otherwise SEO will suffer).
  4. Test in WebPageTest with throttled mobile. Watch the waterfall — the hero image should start loading immediately, not after JavaScript.
  5. Run our page weight tool on a category page before and after. Should see significant total bytes reduction with no LCP regression.

The Performance Impact

Median improvement on a 50-product category page after correct lazy loading setup:

  • Page weight: -65% (only ~10 images load on initial render vs. all 50)
  • Time to Interactive: -1.2s on 4G mobile
  • LCP: usually unchanged (because hero wasn't lazy)
  • CLS: occasionally worsens if width/height not specified — check this

The big win is bandwidth, not first paint. Done right, lazy loading is invisible to the user — they get the same fast first impression, and the browser quietly skips loading 80% of the page's image weight that they'd never have scrolled to anyway.

Done wrong, you slow down the most important moment on every page. Audit yours.

lazy loadingimage performanceCore Web VitalsLCPecommerce performance

See these issues on your store?

Run a free scan and find out in seconds.

Run Free Scan