Sub Category

Latest Blogs
How to Use Lazy Loading & Optimisation Techniques for Image-Heavy Sites

How to Use Lazy Loading & Optimisation Techniques for Image-Heavy Sites

How to Use Lazy Loading & Optimisation Techniques for Image-Heavy Sites

If your website is packed with rich photography, product galleries, magazine-style imagery, hero banners, icons, or user-generated visuals, you already know the challenge: images make your brand look great, but they can also slow your pages down and hurt conversions, SEO, and user satisfaction. The good news is that modern browsers and tooling give you powerful control over how, when, and in what quality images are delivered. Lazy loading, responsive images, modern file formats, and smart delivery strategies can transform an image-heavy site from sluggish to snappy.

In this comprehensive guide, you will learn not only how to toggle on lazy loading, but also how to combine it with advanced optimisation techniques to boost Core Web Vitals, reduce bandwidth costs, and serve consistently crisp images across devices — without sacrificing aesthetics or discoverability.

TL;DR

  • Lazy loading defers the loading of below-the-fold images until users are about to see them, cutting initial page weight and speeding up rendering.
  • Use native lazy loading via the loading='lazy' attribute for most images and combine it with responsive images (srcset, sizes) and modern formats (AVIF, WebP).
  • Do not lazy load above-the-fold, critical images like the hero or LCP image; give them priority with fetchpriority='high', decoding='async', and optional preload hints.
  • Maintain layout stability by setting width and height or using the CSS aspect-ratio property to prevent cumulative layout shift.
  • Adopt an image CDN or processing pipeline for resizing, compression, and format negotiation; set caching and content negotiation correctly.
  • Measure and iterate using Core Web Vitals, real-user monitoring, and lab tools; tune thresholds and breakpoints based on real traffic data.

Table of Contents

  1. Why image-heavy sites slow down and what it costs you
  2. What lazy loading is and how it works
  3. When to lazy load and when not to
  4. Native lazy loading with HTML attributes
  5. Lazy loading with IntersectionObserver
  6. Responsive images done right: srcset, sizes, and picture
  7. Modern formats: AVIF, WebP, and progressive JPEG
  8. Compression strategies and automation pipelines
  9. Placeholders, LQIP, blur-up, and SQIP techniques
  10. Background images, icons, and sprites
  11. Lazy loading iframes and video embeds
  12. Core Web Vitals focus: LCP, CLS, and INP
  13. Priority hints, preloading, and decode strategies
  14. Image CDNs, caching headers, and HTTP delivery
  15. Measurement and monitoring: from Lighthouse to RUM
  16. Platform guides: WordPress, Next.js, React, Vue, Shopify, Magento, and headless stacks
  17. SEO and accessibility for lazy-loaded images
  18. Governance: design systems, content workflows, and performance budgets
  19. Step-by-step implementation plan and checklist
  20. FAQs
  21. Call to action
  22. Final thoughts

1) Why image-heavy sites slow down and what it costs you

Images typically account for the largest share of a page’s total bytes. Large hero banners, carousels, product grids, editorial photography, and background textures quickly add up. Without careful optimisation, this leads to:

  • Slower time to first render and first interaction
  • Higher Largest Contentful Paint (LCP) and lower Core Web Vitals scores
  • Layout shifts from images loading without reserved space
  • More data consumed by users, especially on mobile networks
  • Increased bounce rates and lower conversion rates
  • Reduced crawl efficiency and potential ranking impacts

A 100 KB savings might not sound like much, but multiply it by dozens of images and thousands of pageviews, and the impact becomes substantial. Mobile users on constrained connections will especially benefit from better image delivery.

The economics of image optimisation

  • Performance: Faster pages see better engagement and conversion. Even small improvements can move critical KPIs.
  • Infrastructure: Smaller images reduce bandwidth and CDN egress costs, and can lower storage costs when deduplicated or generated on demand.
  • SEO: Faster, stable pages score better on Core Web Vitals, which remain a search ranking system component. Additionally, well-structured, discoverable images can boost Image Search visibility.

2) What lazy loading is and how it works

Lazy loading defers fetching an image until it is needed — that is, when it is about to enter the viewport. This reduces the amount of data required for the initial render and speeds up the first view.

There are two broad approaches:

  • Native lazy loading via the loading='lazy' attribute
  • JavaScript-driven lazy loading, typically using IntersectionObserver to detect when elements approach the viewport and swapping placeholders for real sources

Native lazy loading is now widely supported, simple, and performant. JavaScript approaches offer extra control and custom effects, and they remain useful for legacy browsers or advanced placeholders.

Why lazy loading works

The critical path to render the above-the-fold content is shorter because the browser skips network requests for below-the-fold resources. When combined with responsive images, only the right sizes for the current device are requested, reducing waste further.


3) When to lazy load and when not to

You should almost always lazy load non-critical images that appear below the fold. Typical candidates include:

  • Product grids beyond the first row
  • Blog post images after the introduction
  • Gallery thumbnails and carousel slides not initially in view
  • Testimonials or logos in footers
  • Sidebar images below the initial viewport

Avoid lazy loading for critical, above-the-fold content such as:

  • The LCP element (often the hero image or a large heading)
  • The first product image on a product detail page
  • Branding or key UX visuals that define the first impression

If you lazy load the hero image, you risk delaying LCP significantly. Prioritise it instead using priority hints and appropriate preloading.


4) Native lazy loading with HTML attributes

Most modern browsers support native lazy loading via a single attribute on images and iframes. It is the easiest and most reliable option in 2025 for standard cases.

Basic usage

<img src='gallery-item-1024.jpg' alt='Sunset over the valley' loading='lazy' width='1024' height='768' />

Key points:

  • loading='lazy' tells the browser to defer loading until the image is close to entering the viewport.
  • Always include width and height to reserve space and avoid layout shifts.
  • Use decoding='async' to improve rendering throughput in some cases.
<img src='gallery-item-1024.jpg' alt='Sunset over the valley' loading='lazy' decoding='async' width='1024' height='768' />

Priority hints: fetchpriority

When an image is important, but you are not lazy loading it, you can signal priority using fetchpriority.

<img src='hero-1600.jpg' alt='Hero scene' width='1600' height='900' fetchpriority='high' decoding='async' />

For non-critical images that should be loaded slowly, you can optionally set fetchpriority='low'.

<img src='decorative-ornament.webp' alt='' width='512' height='512' loading='lazy' fetchpriority='low' />

Note: do not set low priority on images that may soon contribute to LCP.

loading attribute options

  • loading='eager': load immediately (default for above-the-fold)
  • loading='lazy': defer until near viewport

Most browsers auto-detect and treat above-the-fold images as eager. Using loading='lazy' for a hero might slow down your LCP.


5) Lazy loading with IntersectionObserver

For nuanced control or to support custom placeholder transitions, IntersectionObserver offers a performant and flexible way to implement lazy loading in vanilla JS or frameworks.

HTML structure with placeholders

<img class='lazy' data-src='photo-1200.webp' alt='Cliffside view' width='1200' height='800' />

Note the use of data-src to store the actual image URL until the observer swaps it in.

JavaScript using IntersectionObserver

const lazyImages = document.querySelectorAll('img.lazy');

const onIntersection = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      const src = img.getAttribute('data-src');
      if (src) {
        img.src = src;
        img.removeAttribute('data-src');
      }
      img.classList.remove('lazy');
      observer.unobserve(img);
    }
  });
};

const io = new IntersectionObserver(onIntersection, {
  root: null,
  rootMargin: '200px 0px',
  threshold: 0.01
});

lazyImages.forEach(img => io.observe(img));

Tips:

  • Use a positive rootMargin (for example 200px) to prefetch images slightly before they enter the viewport, preventing jank.
  • Leave a tiny threshold so close-to-viewport elements are picked up quickly.
  • Consider adding a fade-in or blur-up effect once the real image has loaded.

Fallback for older browsers

IntersectionObserver is widely supported, but for legacy browsers you can lazy load after scroll events with throttling, or load all images if the feature is missing.

if ('IntersectionObserver' in window) {
  // Use the IO strategy above
} else {
  // Simple fallback: load images after window load
  window.addEventListener('load', () => {
    document.querySelectorAll('img.lazy').forEach(img => {
      const src = img.getAttribute('data-src');
      if (src) img.src = src;
    });
  });
}

6) Responsive images done right: srcset, sizes, and picture

Lazy loading helps with when to load. Responsive images decide what to load. Together, they ensure the browser requests an appropriately sized image for the current viewport and DPR (device pixel ratio).

Using srcset with width descriptors

<img
  src='landscape-800.jpg'
  srcset='
    landscape-480.jpg 480w,
    landscape-800.jpg 800w,
    landscape-1200.jpg 1200w,
    landscape-1600.jpg 1600w
  '
  sizes='(max-width: 600px) 90vw, (max-width: 1200px) 80vw, 1200px'
  loading='lazy'
  alt='Mountain landscape'
  width='1600' height='900'
/>

How it works:

  • srcset lists variants and their intrinsic widths. The browser picks the best candidate based on sizes and device DPR.
  • sizes describes how much viewport width the image will occupy under various conditions. This is critical; without it, the browser may choose a larger-than-needed file.

Art direction with <picture>

Use <picture> to swap not just sizes or formats, but also the actual imagery depending on layout breakpoints.

<picture>
  <source type='image/avif' srcset='hero-800.avif 800w, hero-1600.avif 1600w' sizes='100vw' />
  <source type='image/webp' srcset='hero-800.webp 800w, hero-1600.webp 1600w' sizes='100vw' />
  <img src='hero-1600.jpg' alt='Hero banner' width='1600' height='900' fetchpriority='high' decoding='async' />
</picture>
  • The browser selects the first supported format (AVIF, then WebP, then JPEG fallback).
  • With sizes, you can fine-tune which width variant is selected.
  • Remember to keep the img fallback for older browsers.

sizes fundamentals

Think of sizes as a map from CSS breakpoints to the expected layout width of the image. Common patterns:

  • Full-bleed hero: sizes='100vw'
  • Centered column on desktop: sizes='(min-width: 1200px) 1200px, 90vw'
  • Two-column grid: sizes='(min-width: 1024px) 50vw, 90vw'

Getting sizes right is one of the biggest wins for image-heavy sites, ensuring the browser does not overfetch large files.

Device Pixel Ratio and density descriptors

For icons and small images, you can use density descriptors (1x, 2x) instead of widths.

<img src='icon@1x.png' srcset='icon@1x.png 1x, icon@2x.png 2x' alt='App icon' width='64' height='64' loading='lazy' />

7) Modern formats: AVIF, WebP, and progressive JPEG

File format choice significantly affects size and quality.

  • AVIF: Usually delivers the best compression at a given quality for photographic images, with excellent sharpness and small files. Encoding is slower but CDNs handle it for you.
  • WebP: Strong compression and broad support; a great default.
  • JPEG: Ubiquitous fallback. Progressive JPEG can improve perceived loading by scanning from low to high quality as bytes arrive.
  • PNG: For sharp-edged artwork and transparency; consider WebP or AVIF with alpha for better compression.
  • SVG: For logos, icons, and illustrations. Vector images stay crisp at any resolution and can be minified and inlined.

Practical recommendation

  • Serve AVIF when supported, fall back to WebP, then JPEG.
  • For UI icons and simple shapes, prefer SVG.
  • Avoid GIF for animations; use video or animated WebP/AVIF when possible.

Sample <picture> with AVIF and WebP

<picture>
  <source type='image/avif' srcset='product-600.avif 600w, product-1200.avif 1200w' sizes='(max-width: 600px) 90vw, 600px' />
  <source type='image/webp' srcset='product-600.webp 600w, product-1200.webp 1200w' sizes='(max-width: 600px) 90vw, 600px' />
  <img src='product-1200.jpg' alt='Product close-up' width='1200' height='1200' loading='lazy' decoding='async' />
</picture>

8) Compression strategies and automation pipelines

Compression is not one-size-fits-all. Striking the right balance between visual quality and file size requires strategy.

Lossy vs lossless

  • Lossy (JPEG, WebP, AVIF): Reduces file size by discarding some information, typically imperceptible at moderate settings.
  • Lossless (PNG, WebP lossless, AVIF lossless): Perfect fidelity but larger files; use for graphics or when exact reproduction matters.

Quality settings

  • JPEG: Typical quality between 60 and 80 covers many cases.
  • WebP: Quality around 60 to 75 performs well.
  • AVIF: Quality scales differ; often around 30 to 50 for near-photographic quality, but tune based on encoder and content.

Tools

  • Squoosh: A great visual tool for exploring settings.
  • ImageMagick and libvips: Command-line tools for batch processing.
  • Sharp (Node.js): A fast library for server or build-time processing.
  • MozJPEG, Guetzli: Advanced JPEG encoders; MozJPEG tends to be practical.

Example: generating responsive images with Sharp

const sharp = require('sharp');

const sizes = [480, 800, 1200, 1600];
const formats = ['avif', 'webp', 'jpeg'];

async function processImage(input, baseName) {
  for (const width of sizes) {
    const pipeline = sharp(input).resize({ width, withoutEnlargement: true });

    await pipeline.clone().avif({ quality: 45 }).toFile(`${baseName}-${width}.avif`);
    await pipeline.clone().webp({ quality: 70 }).toFile(`${baseName}-${width}.webp`);
    await pipeline.clone().jpeg({ quality: 75, progressive: true }).toFile(`${baseName}-${width}.jpg`);
  }
}

processImage('hero-original.jpg', 'hero').catch(console.error);

Automate during CI or on the edge

  • Build-time: Pre-generate assets during CI/CD and upload to a storage bucket or CDN.
  • On-demand: Use an image CDN (Cloudinary, Imgix, ImageKit, Fastly IO, Akamai Image Manager) to transform images via URL params and cache variants at the edge.

On-demand pipelines reduce storage complexity and serve only what is needed per request.


9) Placeholders, LQIP, blur-up, and SQIP techniques

A great UX does not show empty boxes. Use lightweight placeholders while the full image loads.

  • LQIP (Low-Quality Image Placeholder): A tiny, heavily compressed version of the original scaled up, optionally blurred.
  • Blur-up: Start with a blurred placeholder and transition to the full image once loaded.
  • SQIP (SVG-based LQIP): Vectorized, abstract placeholders that hint at the final composition.
  • Dominant color: A single color or simple gradient that matches the final image’s average color.

Blur-up example with CSS

<style>
  .placeholder { filter: blur(20px); transform: scale(1.05); transition: filter 300ms ease, transform 300ms ease; }
  .loaded { filter: blur(0); transform: scale(1); }
</style>

<img
  class='lazy placeholder'
  data-src='gallery-1200.webp'
  src='gallery-20.webp'
  alt='Gallery piece'
  width='1200' height='800'
/>

<script>
  const images = document.querySelectorAll('img.lazy');
  const io = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        const fullSrc = img.getAttribute('data-src');
        const tmp = new Image();
        tmp.src = fullSrc;
        tmp.onload = () => {
          img.src = fullSrc;
          img.classList.remove('placeholder');
          img.classList.add('loaded');
        };
        io.unobserve(img);
      }
    });
  }, { rootMargin: '200px' });

  images.forEach(img => io.observe(img));
</script>

This pattern shows a tiny placeholder right away and smoothly transitions to the full image once it is fully loaded, avoiding half-rendered visuals.

Dominant color box using CSS aspect ratio

<style>
  .ratio-box { position: relative; width: 100%; aspect-ratio: 4 / 3; background: #cdb4db; }
  .ratio-box img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
</style>
<div class='ratio-box'>
  <img src='thumb-20.avif' data-src='thumb-1200.avif' alt='Pastel flowers' class='lazy' />
</div>

The aspect-ratio ensures space is reserved, preventing layout shift while the high-res image loads.


10) Background images, icons, and sprites

Background images in CSS do not have a native lazy loading attribute. Use strategies to delay or conditionally load them.

Lazy load background images with a data attribute

<div class='hero-bg lazy-bg' data-bg='hero-1600.avif'>
  <h1>Welcome</h1>
</div>

<style>
  .hero-bg { min-height: 60vh; background-size: cover; background-position: center; }
</style>

<script>
  const lazyBackgrounds = document.querySelectorAll('.lazy-bg');
  const io = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const el = entry.target;
        const url = el.getAttribute('data-bg');
        el.style.backgroundImage = `url(${url})`;
        el.classList.remove('lazy-bg');
        io.unobserve(el);
      }
    });
  });
  lazyBackgrounds.forEach(el => io.observe(el));
</script>

Consider alternatives for icons

  • Use inline SVG for icons and logos where possible. They are crisp at any scale and styleable with CSS.
  • Icon fonts are less accessible and often heavier; prefer SVG sprites or components.

11) Lazy loading iframes and video embeds

Media embeds can be heavy. Lazy load them to avoid blocking the main thread and network.

Native lazy loading for iframes

<iframe
  src='https://www.youtube-nocookie.com/embed/VIDEO_ID'
  title='Demo video'
  loading='lazy'
  width='560' height='315'
  allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'
  allowfullscreen
></iframe>

Click-to-load YouTube pattern

Replace the iframe with a thumbnail until the user clicks play.

<div class='video'>
  <button class='video__btn' aria-label='Play video'>
    <img src='https://i.ytimg.com/vi/VIDEO_ID/hqdefault.jpg' alt='Video thumbnail' width='480' height='270' loading='lazy' />
  </button>
</div>

<script>
  document.querySelectorAll('.video__btn').forEach(btn => {
    btn.addEventListener('click', () => {
      const wrapper = btn.parentElement;
      const iframe = document.createElement('iframe');
      iframe.src = 'https://www.youtube-nocookie.com/embed/VIDEO_ID?autoplay=1';
      iframe.title = 'Demo video';
      iframe.width = '560';
      iframe.height = '315';
      iframe.allow = 'autoplay; encrypted-media; picture-in-picture';
      iframe.allowFullscreen = true;
      wrapper.innerHTML = '';
      wrapper.appendChild(iframe);
    });
  });
</script>

This approach drastically reduces initial payloads on pages with many embedded videos.


12) Core Web Vitals focus: LCP, CLS, and INP

Your image strategy should be aligned with Core Web Vitals.

LCP: Largest Contentful Paint

Often the LCP is a hero image or a large block of text. Improve LCP by:

  • Not lazy loading the LCP image
  • Using fetchpriority='high'
  • Preloading the right resource (the exact URL and variant that will be used)
  • Hosting images on a fast CDN close to users
  • Minimising render-blocking CSS and JS to speed layout and paint

CLS: Cumulative Layout Shift

Images without reserved dimensions cause content to jump around. Prevent CLS by:

  • Providing width and height attributes for images so the browser can compute layout before the image downloads
  • Using CSS aspect-ratio for flexible layouts
  • Avoiding late-inserted ads or content above existing content
  • Reserving space for banners or using placeholders that match the final size

INP: Interaction to Next Paint

Heavy images can contribute indirectly to INP by congesting the network and main thread. Optimisation helps by reducing decode time and CPU overhead.


13) Priority hints, preloading, and decode strategies

Preloading the LCP image

If you know which image will be the LCP element, preload it. Make sure to preload the exact resource you will use, including format and size.

<link
  rel='preload'
  as='image'
  href='/images/hero-1600.avif'
  imagesrcset='/images/hero-800.avif 800w, /images/hero-1600.avif 1600w'
  imagesizes='100vw'
  fetchpriority='high'
/>
  • Use imagesrcset and imagesizes with rel='preload' to guide the browser to the right variant.
  • Avoid preloading too many images; over-preloading can backfire.

Decode strategies

  • decoding='async' lets the browser decode images off the main thread when possible.
  • For the LCP image, decoding='async' generally helps; test with and without.

The right balance

Overusing high priority or preloading everything defeats the purpose. Reserve these for critical images, typically the LCP and above-the-fold elements.


14) Image CDNs, caching headers, and HTTP delivery

An image CDN can transform, compress, and cache your images at the edge, handling device differences, formats, and geographies.

Benefits of an image CDN

  • On-the-fly resizing and format conversion (AVIF, WebP)
  • Smart quality tuning and auto-cropping
  • Adaptive delivery based on client support
  • Caching at global edge locations with low latency

Caching headers

  • Use Cache-Control: public, max-age=31536000, immutable for fingerprinted or content-addressed images.
  • For originals that may change, adopt versioned URLs or ETags and shorter max-ages.
  • Always ensure long-lived caching for transformed variants when content is immutable.

Content negotiation

If you rely on server-side format negotiation, set the Vary: Accept header to reflect that the response may vary by client capabilities. However, prefer explicit URLs when possible for caching clarity.

HTTP/2 and HTTP/3 delivery

  • Consolidate connections and leverage multiplexing.
  • Use preconnect to your image CDN if it is on a different domain.
<link rel='preconnect' href='https://images.example-cdn.com' crossorigin>
  • Consider dns-prefetch for subresources loaded late.
<link rel='dns-prefetch' href='//images.example-cdn.com'>

15) Measurement and monitoring: from Lighthouse to RUM

Optimisation without measurement is guesswork. Use both lab and field data.

Lab tools

  • Lighthouse and PageSpeed Insights: Provide diagnostics and scores based on simulated conditions.
  • WebPageTest: Deep waterfalls, filmstrips, and advanced scripting. Great for comparing strategies.
  • Chrome DevTools: Performance panel, network waterfalls, and coverage.

Field data and RUM

  • Core Web Vitals are evaluated from real user experiences when sufficient data exists.
  • Use a RUM solution or the web-vitals library to capture LCP, CLS, and INP.
<script type='module'>
  import { onLCP, onCLS, onINP } from 'https://unpkg.com/web-vitals@4/dist/web-vitals.js';

  function sendToAnalytics(metric) {
    navigator.sendBeacon('/vitals', JSON.stringify(metric));
  }

  onLCP(sendToAnalytics);
  onCLS(sendToAnalytics);
  onINP(sendToAnalytics);
</script>

What to track

  • LCP and CLS distributions by page type
  • LCP candidate element and resource identification
  • Image format ratios and byte savings
  • Error rates, broken images, and failed transforms
  • CDN cache hit rates and origin latency

16) Platform guides: WordPress, Next.js, React, Vue, Shopify, Magento, and headless stacks

Every platform has nuances. Use built-in tools where appropriate.

WordPress

  • Native lazy loading: Since WordPress 5.5, images get loading='lazy' by default.
  • Consider excluding the first contentful images from lazy loading. Themes can control this via filters.
// functions.php
add_filter('wp_lazy_loading_enabled', function($default, $tag_name, $context) {
  if ($tag_name === 'img' && $context === 'the_content') {
    // Keep lazy loading but you can add logic to skip the first image
  }
  return $default;
}, 10, 3);
  • Plugins for image optimisation: ShortPixel, Imagify, EWWW, Optimole, Smush. Many offer WebP/AVIF conversion and CDN integration.
  • Ensure theme markup includes width and height to prevent CLS; WordPress core adds these when possible.
  • For background images defined in CSS or page builder blocks, consider lazy-loading strategies via JavaScript.

Next.js

  • Use the next/image component for automatic optimisations, responsive sizing, and lazy loading.
import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src='/images/hero-1600.jpg'
      alt='Hero'
      width={1600}
      height={900}
      priority
      sizes='100vw'
      style={{ width: '100%', height: 'auto' }}
    />
  );
}
  • priority signals that this image is critical (do not lazy load). For other images, next/image lazy loads by default.
  • Configure remote patterns in next.config.js if images are on an external domain.
  • Consider using the new Next.js Image CDN or a compatible third-party loader for AVIF and edge transforms.

React (vanilla)

  • Third-party packages like react-intersection-observer or libraries that implement blur-up can simplify lazy loading.
  • You can also implement a small custom hook using IntersectionObserver.
import { useEffect, useRef, useState } from 'react';

function useIntersection(options) {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const node = ref.current;
    if (!node) return;
    const io = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          setVisible(true);
          io.disconnect();
        }
      });
    }, options);
    io.observe(node);
    return () => io.disconnect();
  }, [options]);

  return [ref, visible];
}

export function LazyImage({ placeholder, src, alt, ...rest }) {
  const [ref, visible] = useIntersection({ rootMargin: '200px' });
  const [loaded, setLoaded] = useState(false);

  return (
    <img
      ref={ref}
      src={visible ? src : placeholder}
      alt={alt}
      onLoad={() => setLoaded(true)}
      className={loaded ? 'loaded' : 'placeholder'}
      {...rest}
    />
  );
}

Vue and Nuxt

  • Vue directives like v-intersect and plugins like vue-lazyload offer simple integration.
  • Nuxt Image module provides a high-level abstraction for responsive and optimised image delivery, often with an integrated CDN.
<template>
  <NuxtImg src='/images/product.jpg' sizes='(max-width: 768px) 90vw, 600px' format='webp' alt='Product' />
</template>

Shopify

  • Most modern Shopify themes use lazy loading by default for product list images.
  • Use Liquid filters to generate the right size and format.
<img
  src='{{ product.featured_image | image_url: width: 800 }}'
  srcset='{{ product.featured_image | image_url: width: 400 }} 400w, {{ product.featured_image | image_url: width: 800 }} 800w'
  sizes='(max-width: 600px) 90vw, 800px'
  alt='{{ product.title | escape }}'
  loading='lazy'
  width='800' height='800'
/>
  • Consider a third-party image CDN integration if you need AVIF, fine-tuned compression, or art direction.

Magento

  • Ensure templates output width and height attributes for images.
  • Use Magento modules or build-time scripts for WebP/AVIF conversion and multiple sizes.
  • Integrate an image CDN for transformation and edge caching.

Headless CMS and JAMstack

  • Most headless CMS platforms integrate with an image CDN out of the box, providing URL-based transformations.
  • Use GraphQL or REST APIs that return multiple widths and formats and assemble <picture> or srcset accordingly.

17) SEO and accessibility for lazy-loaded images

Optimising images is not just about speed. It is also about discoverability and inclusivity.

Alt text and semantics

  • Provide concise, descriptive alt text for meaningful images.
  • Use empty alt text for purely decorative images to avoid unnecessary verbosity for screen reader users.
  • Associate captions with <figure> and <figcaption> when appropriate.

Search engine considerations

  • Modern search engines can execute JS and discover lazy-loaded images, but do not make them work too hard. Prefer native lazy loading or simple IO-based patterns.
  • Avoid hiding images behind unconventional mechanisms; ensure that source URLs are in the markup or easily discoverable.
  • Historically, some sites included <noscript> fallbacks with direct <img> tags. While less necessary today, it can help if your JS is critical for image loading and might fail.
  • Maintain an image sitemap for large catalogs or galleries to explicitly list key images.

Structured data

  • When using schema for products, articles, or recipes, include image fields with fully qualified URLs.
  • Ensure the images referenced in structured data are accessible and reasonably sized.

Infinite scroll and pagination

  • For SEO, make sure crawlable pagination or load-more links exist, not just infinite scroll that requires user interaction.
  • Lazy load images within each page of content to control resource usage.

18) Governance: design systems, content workflows, and performance budgets

For image optimisation to stick, it must be baked into your processes.

Design system considerations

  • Standardise breakpoints and aspect ratios across components.
  • Provide component APIs for images that require width, height, alt, and priority flags.
  • Offer design tokens for spacing and ratio to prevent ad-hoc sizes that complicate responsive rules.

Content workflows

  • Provide editorial guidance on ideal source resolution and framing.
  • Automate conversion to modern formats and multiple sizes on upload.
  • Flag oversized images during QA or in a CMS validation step.

Performance budgets

  • Define budgets for LCP, CLS, and total image bytes per template.
  • Integrate budgets into CI with automated checks.

19) Step-by-step implementation plan and checklist

Here is a practical plan to implement lazy loading and image optimisation on an image-heavy site.

Step 1: Audit

  • Inventory all image usage by template: hero, product grid, article body, banners, background images, icons, embeds.
  • Capture current metrics: LCP, CLS, INP, total bytes, number of image requests, average image dimensions.
  • Identify the LCP element per template and determine if it is an image.

Step 2: Decide critical vs non-critical

  • Mark above-the-fold images as eager, with fetchpriority='high' for the LCP candidate.
  • Mark below-the-fold images for lazy loading.

Step 3: Implement responsive images

  • For each component, define srcset and sizes that reflect actual layout.
  • Use <picture> for art direction or format switching.
  • Set width and height attributes or CSS aspect-ratio to prevent CLS.

Step 4: Adopt modern formats

  • Choose AVIF and WebP as primary, with JPEG fallback.
  • Use an image CDN or a build-time pipeline for conversions.

Step 5: Add placeholders where needed

  • Choose blur-up, dominant color, or SQIP based on design aesthetics.
  • Implement IntersectionObserver to swap placeholders smoothly.

Step 6: Optimise delivery and caching

  • Serve images from a fast CDN; add preconnect if cross-origin.
  • Configure caching headers with long max-age for versioned assets.
  • Add Vary: Accept if negotiating formats server-side.

Step 7: Prioritise LCP

  • Preload the LCP resource using rel='preload' and fetchpriority='high'.
  • Ensure the markup references the same URL and variants as preload.
  • Avoid lazy loading the LCP image.

Step 8: Monitor and iterate

  • Track Core Web Vitals in production using RUM.
  • Check Google Search Console Core Web Vitals and Page Experience reports.
  • Iterate breakpoints, quality settings, and placeholder strategies.

Quick checklist

  • Lazy load all non-critical images
  • Do not lazy load LCP image; set priority
  • Provide width and height or aspect-ratio to prevent CLS
  • Use srcset and sizes for responsive delivery
  • Serve AVIF and WebP with JPEG fallback
  • Compress to appropriate quality levels
  • Use an image CDN or automated pipeline
  • Preload hero with the exact URL and sizes
  • Add placeholders for long-loading visuals
  • Monitor and adjust continuously

20) FAQs

Does lazy loading hurt SEO by hiding images from crawlers

Modern crawlers can process native lazy loading and standard IO-based lazy loading patterns. Make sure images are in the HTML markup with proper attributes. For complex, JS-only scenarios, consider a simple <noscript> fallback for critical content or ensure server-side rendering provides initial markup. Also maintain image sitemaps for large catalogs.

Should I lazy load the first image on a product detail page

Usually no. The first image is often your LCP element. Load it eagerly with fetchpriority='high', decoding='async', and consider preloading. Lazy load additional gallery images and thumbnails.

How many breakpoints should I generate for responsive images

Enough to span your design’s major breakpoints and track actual traffic. Common widths include 320, 480, 640, 768, 1024, 1280, 1536, 1920. Use analytics to reduce rarely used sizes. Avoid generating too many variants that bloat storage.

Is AVIF always better than WebP

AVIF often yields smaller files for photographs at similar perceptual quality, but encoding can be slower, and results vary by content. Keep WebP as a reliable fallback. Test visually and measure bytes saved. Some workflows still prefer WebP for a balance of speed and quality.

Should I use progressive JPEGs

Progressive JPEG can improve perceived loading on slow connections by displaying a coarse version early. If your pipeline supports it, it is a reasonable default for JPEG. However, when serving AVIF or WebP, progressive JPEG may be less relevant as a primary format.

Do I need JavaScript lazy loading if native loading is supported

For most use cases, native lazy loading works well and is simpler. Use JavaScript-based lazy loading if you require custom effects, need to lazy load CSS background images, or must support advanced placeholder animations and thresholds.

Can lazy loading increase CLS

Yes, if you do not reserve space. Always specify width and height or use aspect-ratio to prevent layout jumps. Also avoid inserting new elements above existing content after load.

How do I lazy load background images

Use IntersectionObserver to detect when an element enters the viewport and set el.style.backgroundImage to the actual URL. Use a placeholder background color and reserve space using CSS aspect-ratio or fixed height to prevent shifts.

Should I preload every image on the page

No. Preloading is for critical resources only. Preloading too many assets delays other essential work and can worsen performance. Preload the LCP image and any unavoidable above-the-fold images.

How can I validate that my sizes attribute is correct

Use the browser’s DevTools to inspect which image candidate is downloaded at different viewport widths. If the browser frequently downloads a larger resource than necessary, your sizes rules may be too generous. Use WebPageTest to see candidate choices across devices.


21) Call to action

Want a personalised performance audit of your image-heavy pages with actionable steps and code-level recommendations

  • Request a free Core Web Vitals scan and image audit.
  • Get a tailored optimisation plan for your CMS, storefront, or framework.
  • Implement fast with our developer-ready components and CI recipes.

Let us help you elevate visual quality without sacrificing speed or SEO. Reach out today to schedule your consultation.


22) Final thoughts

Success with image-heavy sites is about balance. Your users expect rich, immersive visuals, but they also demand speed, stability, and responsiveness. Lazy loading is a cornerstone tactic, but it reaches its full potential only when combined with responsive images, modern formats, solid caching, and a culture of continuous measurement.

Start with the basics: do not lazy load the hero, mark everything else lazy, reserve space to avoid CLS, and serve the right size and format. Then iterate: refine sizes, adopt an image CDN, add placeholders, and monitor field data. As you embed these practices into your design system and content workflows, performance becomes a default, not an afterthought.

The payoff is substantial: happier users, stronger Core Web Vitals, better SEO, and a healthier bottom line. Your images can dazzle without dragging you down — and now you have the playbook to make it happen.

Share this article:
Comments

Loading comments...

Write a comment
Article Tags
lazy loadingimage optimizationCore Web VitalsLargest Contentful PaintCumulative Layout Shiftresponsive imagessrcsetsizes attributeAVIFWebPimage CDNIntersectionObserverNext.js ImageWordPress image optimizationecommerce performancepriority hintsfetchpriorityPageSpeed InsightsLighthouseimage compression