PerformanceMay 23, 20267 min read

Image Lazy Loading: How to Use It Without Hurting Your LCP Score

Lazy loading images speeds up your store — unless you apply it to the wrong images. Here's which images to lazy load, which to eager load, and how to avoid the LCP penalty.

StoreVitals Team

Lazy loading is one of the most effective performance tools for ecommerce stores. Image-heavy product catalogs can load dozens of images per page — lazy loading defers the ones below the fold until the user scrolls near them, dramatically reducing initial page weight and bandwidth consumption.

But applied carelessly, lazy loading causes one of the most common LCP (Largest Contentful Paint) failures in ecommerce: the hero image — your most important above-the-fold element — gets lazy-loaded, causing it to load seconds after everything else and tanking your Core Web Vitals score.

How Lazy Loading Works

The native lazy loading attribute is a single HTML attribute:

<img src="product-photo.jpg" alt="Product name" loading="lazy" />

When the browser encounters this, it skips fetching the image until it's within a certain distance of the viewport (the "eagerness threshold" — typically one full screen height ahead of the current scroll position in Chrome). This means images far below the fold aren't downloaded until the user scrolls toward them.

The counterpart is loading="eager" — but this is the default behavior for all images that don't have loading="lazy", so you almost never need to specify it explicitly. The exception is when you're overriding a lazy load on specific images.

The LCP Problem: When Lazy Loading Goes Wrong

The most common lazy loading mistake in ecommerce is applying loading="lazy" to the hero image or the primary product image on product pages. These are almost always the LCP element — the largest visible image on the page at load time.

When you lazy-load your LCP image:

  1. The browser parses HTML and encounters the image tag
  2. Instead of fetching it, the browser defers it (it's "lazy")
  3. The browser continues loading scripts, stylesheets, fonts
  4. Eventually the browser determines the image is in or near the viewport
  5. Only then does it start fetching the image
  6. The image loads seconds after the page is otherwise visually complete
  7. LCP = the time the image finishes loading = very late = poor score

This is a PageSpeed Insights "opportunity" item that shows as "Largest Contentful Paint image was lazily loaded." Google explicitly flags it because it's so damaging to LCP.

The Rule: Above-the-Fold = Eager, Below-the-Fold = Lazy

The decision framework is simple:

  • Eager load (never add lazy): Hero images, primary product images, banner images, any image that appears above the fold on a median viewport size (1080px wide, 768px tall desktop or 375px × 667px mobile)
  • Lazy load: Secondary product images (gallery thumbnails below the main image), product grid images that appear below the fold, customer review images, related product carousels, footer content images

The ambiguous zone is the product image grid on collection pages — the first 4-6 products are typically above or near the fold, the rest are below. A pragmatic approach:

  • First product (the featured/largest image): always eager
  • Products 2-6: no lazy attribute (browser default eager)
  • Products 7+: lazy load

Next.js / React: The fetchpriority Attribute

Modern browsers support a fetchpriority attribute that goes further than eager/lazy — it lets you tell the browser to prioritize an image fetch over everything else:

<img
  src="/hero.webp"
  alt="Summer sale — up to 50% off"
  fetchpriority="high"
  loading="eager"
/>

In Next.js, the Image component handles this automatically when you set priority={true}:

import Image from 'next/image';

// Hero image — always priority
<Image
  src="/hero.webp"
  alt="Summer sale"
  width={1200}
  height={600}
  priority  // equivalent to fetchpriority="high" + loading="eager" + preload link
/>

// Below-fold product images — lazy by default
<Image
  src={product.imageUrl}
  alt={product.title}
  width={400}
  height={400}
  // no priority prop = lazy by default in Next.js
/>

The Next.js Image component with priority also adds a <link rel="preload"> element to the document head for the image — combining the preload hint with the high fetch priority for maximum LCP impact.

Shopify Themes: How to Check Your Current Setup

In Shopify Liquid themes, image lazy loading is typically controlled at the theme level. Check your theme's image.liquid snippet or wherever the main product image renders. Look for:

  • loading="lazy" applied to the featured product image (bad — remove it)
  • Missing fetchpriority="high" on the hero image (add it)
  • The Dawn theme (Shopify's reference theme) correctly eager-loads the product image — if your customization re-wrapped it, verify it didn't inadvertently add lazy loading

Liquid example for product page hero image:

<img
  src="{{ product.featured_image | img_url: '1200x' }}"
  srcset="{{ product.featured_image | img_url: '600x' }} 600w,
          {{ product.featured_image | img_url: '1200x' }} 1200w"
  alt="{{ product.featured_image.alt | escape }}"
  width="1200"
  height="{{ 1200 | divided_by: product.featured_image.aspect_ratio | round }}"
  fetchpriority="high"
  {%- comment -%}NO loading="lazy" here — this is the LCP image{%- endcomment -%}
/>

The Width and Height Attributes: Critical for CLS

Adding width and height attributes to every image (lazy or eager) is required to prevent Cumulative Layout Shift. When the browser doesn't know an image's dimensions before it loads, the page layout shifts when the image appears — hurting both CLS score and user experience. This is separate from lazy loading but worth mentioning as part of any image audit.

<!-- Wrong: no dimensions = layout shift when image loads -->
<img src="product.jpg" alt="Product" loading="lazy" />

<!-- Right: explicit dimensions = space reserved, no layout shift -->
<img src="product.jpg" alt="Product" width="400" height="400" loading="lazy" />

Quick Audit: Find Your Lazy-Loaded LCP Images

Run this in Chrome DevTools Console on your product page and homepage:

// Find all lazy images
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
  const rect = img.getBoundingClientRect();
  if (rect.top < window.innerHeight) {
    console.warn('LAZY ABOVE FOLD:', img.src);
  }
});

Any image logged is a likely LCP issue. Remove the loading="lazy" attribute from above-fold images and retest with PageSpeed Insights.

StoreVitals checks for large above-the-fold images and render-blocking resources as part of its Performance pillar audit. Run a free scan to see your store's current performance health score.

lazy loadingLCPCore Web Vitalsperformanceimagesecommerce

See these issues on your store?

Run a free scan and find out in seconds.

Run Free Scan