Detailed guide

Lazy Loading Best Practices Guide

View contents

Lazy Loading Best Practices: When and When NOT to Use It

Introduction

Lazy loading is one of the most effective techniques for improving page performance—when used correctly. By deferring the loading of off-screen images, you reduce initial page weight, decrease Time to Interactive (TTI), and save bandwidth for users who don’t scroll to see all content.

However, misapplied lazy loading can hurt performance instead of helping it. The most critical mistake is lazy loading your LCP (Largest Contentful Paint) image, which can add 500ms+ to your most important Core Web Vital.

This comprehensive guide covers everything you need to know about implementing lazy loading effectively: when to use it, when to avoid it, placeholder strategies, and advanced optimization techniques.


Understanding Lazy Loading

What is Lazy Loading?

Lazy loading defers the loading of resources until they’re needed. For images, this typically means:

  1. Images start as placeholders (blank, blur, or low-quality)
  2. As the user scrolls, images near the viewport trigger loading
  3. Once loaded, the full image replaces the placeholder

Benefits of Lazy Loading

  • Faster initial page load: Fewer resources to download upfront
  • Reduced bandwidth: Users only download images they actually see
  • Lower server load: Fewer simultaneous requests
  • Better TTI: Less JavaScript blocking from image decoding
  • Improved Core Web Vitals: When applied correctly

Native vs JavaScript Lazy Loading

There are two main approaches:

Native Lazy Loading (Recommended):

<img src="photo.jpg" alt="Description" loading="lazy">

JavaScript Lazy Loading:

// Using Intersection Observer
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
});

Native lazy loading is now supported by all major browsers (97%+ support) and should be your default choice.


When to Use Lazy Loading

Ideal Candidates for Lazy Loading

1. Images Below the Fold Any image that requires scrolling to see is a good candidate:

<!-- First screen: NO lazy loading -->
<img src="hero.jpg" alt="Hero">

<!-- Below fold: YES lazy loading -->
<img src="product-2.jpg" alt="Product" loading="lazy">
<img src="product-3.jpg" alt="Product" loading="lazy">

2. Product Grids (After First Row)

<!-- First row visible on load -->
<img src="product-1.jpg" alt="Product 1">
<img src="product-2.jpg" alt="Product 2">
<img src="product-3.jpg" alt="Product 3">
<img src="product-4.jpg" alt="Product 4">

<!-- Remaining products: lazy load -->
<img src="product-5.jpg" alt="Product 5" loading="lazy">
<img src="product-6.jpg" alt="Product 6" loading="lazy">
<!-- ... more products -->

3. Image Galleries and Carousels

<!-- Active slide: NO lazy loading -->
<img src="slide-1.jpg" alt="Slide 1" class="active">

<!-- Other slides: YES lazy loading -->
<img src="slide-2.jpg" alt="Slide 2" loading="lazy">
<img src="slide-3.jpg" alt="Slide 3" loading="lazy">

4. Blog Post Thumbnails

<article>
  <img src="thumb.jpg" alt="Article" loading="lazy">
  <h2>Article Title</h2>
</article>

5. User-Generated Content Comments, reviews, and social feeds often contain many images:

<div class="comment">
  <img src="avatar.jpg" alt="User" loading="lazy">
  <p>Comment text...</p>
</div>

When NOT to Use Lazy Loading

Critical Cases to Avoid

1. LCP Image (Most Important)

The Largest Contentful Paint image should load immediately:

<!-- WRONG: Don't lazy load LCP -->
<img src="hero.jpg" alt="Hero" loading="lazy">

<!-- CORRECT: No lazy loading, add priority -->
<img src="hero.jpg" alt="Hero" fetchpriority="high">

2. Above-the-Fold Content

Anything visible on initial page load:

<!-- All visible without scrolling: no lazy loading -->
<header>
  <img src="logo.png" alt="Company Logo">
</header>
<main>
  <img src="hero.jpg" alt="Hero" fetchpriority="high">
</main>

3. Small Images (< 10KB)

The overhead of lazy loading may exceed the savings:

<!-- Icons and small images: load normally -->
<img src="icon.svg" alt="" width="24" height="24">

4. Critical Product Images

On e-commerce product pages, the main product image is usually LCP:

<!-- Main product image: no lazy loading -->
<img src="product-main.jpg" alt="Nike Air Max" fetchpriority="high">

<!-- Thumbnail gallery: lazy load -->
<img src="product-angle-2.jpg" alt="Side view" loading="lazy">
<img src="product-angle-3.jpg" alt="Back view" loading="lazy">

Placeholder Strategies

Good placeholders prevent layout shift while communicating that content is loading.

1. Aspect Ratio Preservation

Always include width and height to prevent CLS:

<img
  src="photo.jpg"
  alt="Description"
  width="800"
  height="600"
  loading="lazy"
>

With CSS:

img {
  aspect-ratio: 4/3;
  width: 100%;
  height: auto;
}

2. Blur-Up Technique (LQIP)

Load a tiny, blurred version first:

<div class="image-container">
  <img
    src="photo-tiny.jpg"
    data-src="photo.jpg"
    alt="Description"
    class="blur-up"
  >
</div>
.blur-up {
  filter: blur(20px);
  transition: filter 0.3s;
}
.blur-up.loaded {
  filter: blur(0);
}

3. Solid Color Placeholder

Use the dominant color from the image:

<div class="image-wrapper" style="background: #3498db;">
  <img src="photo.jpg" alt="Description" loading="lazy">
</div>

4. Skeleton Loaders

Animated placeholder that indicates loading:

.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: skeleton 1.5s infinite;
}

@keyframes skeleton {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

Combining with Priority Hints

Use fetchpriority alongside lazy loading for optimal results:

<!-- High priority: LCP image -->
<img src="hero.jpg" alt="Hero" fetchpriority="high">

<!-- Default priority: above-fold secondary images -->
<img src="product.jpg" alt="Product">

<!-- Low priority with lazy load: below-fold images -->
<img src="gallery-5.jpg" alt="Gallery" loading="lazy" fetchpriority="low">

Priority + Preload for Critical Images

<head>
  <!-- Preload LCP image -->
  <link rel="preload" as="image" href="hero.webp" type="image/webp">
</head>
<body>
  <img src="hero.webp" alt="Hero" fetchpriority="high">
</body>

Browser Loading Thresholds

Browsers have built-in thresholds for when lazy-loaded images start loading. Understanding these helps predict behavior.

Chrome’s Loading Distance

Chrome begins loading lazy images when they’re within a certain distance of the viewport:

Connection Speed Loading Distance
4G ~1250px
3G ~2500px
2G ~6000px

This means on fast connections, images start loading sooner; on slow connections, the browser waits longer to save bandwidth.

Implications for Design

  • Images in a carousel may start loading before the user swipes
  • “Infinite scroll” content will preload ahead of the viewport
  • Very long pages still benefit from lazy loading

Lazy Loading and SEO

Googlebot’s Behavior

Google can discover and index lazy-loaded images, but with caveats:

  1. Native lazy loading: Fully supported by Googlebot
  2. JavaScript lazy loading: Requires proper implementation
  3. Scroll-triggered loading: May not be indexed (Googlebot doesn’t scroll infinitely)

Best Practices for SEO

<!-- Include real src, not just data-src -->
<img
  src="photo.jpg"
  alt="Descriptive alt text"
  loading="lazy"
>

<!-- BAD: No src until JavaScript runs -->
<img
  data-src="photo.jpg"
  alt="Photo"
  src="placeholder.gif"
>

Structured Data Considerations

For product images and other schema markup, ensure the main image loads without JavaScript:

<script type="application/ld+json">
{
  "@type": "Product",
  "image": "https://example.com/product.jpg"
}
</script>

Framework-Specific Implementation

Next.js

import Image from 'next/image'

// LCP image: eager loading
<Image src="/hero.jpg" priority alt="Hero" />

// Below-fold: lazy loading (default)
<Image src="/gallery.jpg" alt="Gallery" />

Nuxt

<!-- LCP image -->
<NuxtImg src="/hero.jpg" preload alt="Hero" />

<!-- Lazy loaded (default) -->
<NuxtImg src="/gallery.jpg" alt="Gallery" loading="lazy" />

WordPress

Most modern WordPress themes apply lazy loading automatically. To exclude LCP images:

// In functions.php
add_filter('wp_img_tag_add_loading_attr', function($value, $image, $context) {
    if (str_contains($image, 'hero-image')) {
        return false; // Don't add loading="lazy"
    }
    return $value;
}, 10, 3);

Measuring Lazy Loading Impact

Key Metrics to Track

Metric What to Measure
LCP Should improve or stay same (never increase)
TTI Time to Interactive should decrease
Total Blocking Time Should decrease
Page Weight Initial download size should decrease
CLS Should not increase (use aspect ratios)

Tools for Measurement

  1. Lighthouse: Full performance audit
  2. Chrome DevTools Network Tab: See deferred requests
  3. WebPageTest: Filmstrip view shows loading sequence
  4. CrUX Dashboard: Real-user data over time

Common Mistakes and How to Fix Them

1. All Images Lazy Loaded

Problem: CMS or framework applies lazy loading globally Fix: Exclude first-screen images, especially LCP

2. Missing Dimensions

Problem: Images cause layout shift when loading Fix: Always include width/height or use aspect-ratio

3. Aggressive Thresholds

Problem: Images load too late, visible loading flicker Fix: Use native lazy loading or increase rootMargin

4. No Placeholders

Problem: Empty space jumps to filled space Fix: Implement blur-up, skeleton, or color placeholders


Implementation Checklist

  • [ ] LCP image does NOT have loading="lazy"
  • [ ] LCP image has fetchpriority="high"
  • [ ] Above-fold images are not lazy loaded
  • [ ] Below-fold images have loading="lazy"
  • [ ] All lazy images have width/height attributes
  • [ ] Placeholder strategy prevents layout shift
  • [ ] SEO: Images have real src (not just data-src)
  • [ ] Framework defaults reviewed and adjusted
  • [ ] Performance tested with Lighthouse


References

  1. MDN - loading attribute - Technical specification
  2. web.dev - Browser-level lazy loading - Implementation guide
  3. Chrome Developers - Lazy loading images - Lighthouse documentation
  4. web.dev - Native lazy loading - Deep dive on native implementation

Related articles

Related version

Introduction

Lcp Image Lazy Loading Explained

Lazy loading images is widely considered a best practice for web performance

Category hub

Hub

Images Seo Hub

Images are critical to user engagement—but they can also be your website's biggest performance bottleneck

In the same category

Detailed guide

Responsive Images Implementation Guide

Responsive images are one of the most impactful performance optimizations you can implement

Detailed guide

Image Optimization Guide

Images typically account for the largest portion of bytes downloaded on a webpage

Other topics

Detailed guide

Lcp Optimization Guide

Largest Contentful Paint (LCP) measures when the largest content element becomes visible to users