Skip to main content

Lazy Loading Patterns Guide

Lazy loading defers loading of non-critical resources until they're needed, reducing initial page weight and improving LCP. Use native browser APIs for images and dynamic imports for JavaScript components.

✓ Native image lazy loading ✓ React.lazy + Suspense ✓ Intersection Observer ✓ Virtual scrolling

Native Image Lazy Loading

<!-- ✅ Lazy load below-fold images -->
<img
  src="product-gallery-2.webp"
  width="800"
  height="600"
  loading="lazy"
  alt="Product gallery image"
>

<!-- ❌ NEVER lazy-load the LCP/hero image -->
<!-- This INCREASES LCP -->
<img src="hero.webp" loading="lazy" alt="Hero">

<!-- ✅ Hero image: eager + high priority -->
<img
  src="hero.webp"
  width="1200"
  height="600"
  loading="eager"
  fetchpriority="high"
  alt="Hero"
>

<!-- Also lazy-load iframes -->
<iframe
  src="https://www.youtube.com/embed/VIDEO_ID"
  loading="lazy"
  width="560"
  height="315"
></iframe>

React Component Lazy Loading

import { lazy, Suspense } from 'react';

// Lazy load heavy components
const HeavyChart     = lazy(() => import('./components/HeavyChart'));
const MapComponent   = lazy(() => import('./components/MapComponent'));
const VideoPlayer    = lazy(() => import('./components/VideoPlayer'));
const PdfViewer      = lazy(() => import('./components/PdfViewer'));

// Loading skeleton component
function ChartSkeleton() {
  return <div class="h-64 bg-gray-200 animate-pulse rounded" />;
}

// Usage with Suspense
function Dashboard() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChart(true)}>Show Chart</button>
      {showChart && (
        <Suspense fallback={<ChartSkeleton />}>
          <HeavyChart data={data} />
        </Suspense>
      )}
    </div>
  );
}

Custom Intersection Observer Hook

// useIntersectionObserver.ts
import { useEffect, useRef, useState } from 'react';

export function useIntersectionObserver(options = {}) {
  const ref = useRef<HTMLElement>(null);
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;

    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        setIsVisible(true);
        observer.unobserve(el); // Only trigger once
      }
    }, { rootMargin: '200px', ...options });

    observer.observe(el);
    return () => observer.disconnect();
  }, []);

  return { ref, isVisible };
}

// Usage: lazy-load a component on scroll into view
function LazySection() {
  const { ref, isVisible } = useIntersectionObserver();

  return (
    <div ref={ref}>
      {isVisible ? (
        <Suspense fallback={<Skeleton />}>
          <HeavyComponent />
        </Suspense>
      ) : (
        <div style={{ height: 400 }} />  {/* Placeholder to prevent CLS */}
      )}
    </div>
  );
}

Virtual Scrolling for Long Lists

npm install @tanstack/react-virtual
import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }) {
  const parentRef = useRef(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,  // estimated row height in px
    overscan: 10,            // render 10 extra rows above/below
  });

  return (
    <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize() }}>
        {virtualizer.getVirtualItems().map(virtualRow => (
          <div
            key={virtualRow.key}
            style={{
              position: 'absolute',
              top: virtualRow.start,
              height: virtualRow.size,
            }}
          >
            {items[virtualRow.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

When to Use Each Pattern

Pattern Best For Savings
Native loading="lazy"Below-fold images, iframesBandwidth
React.lazy + SuspenseHeavy route pages, modals, chartsJS parse/exec
Intersection ObserverScroll-triggered content, analyticsAPI calls
Virtual scrollingLists with 1000+ itemsDOM nodes, memory

Frequently Asked Questions

What is the difference between lazy loading images and lazy loading components?▼

Image lazy loading defers downloading the image file until the <img> element is near the viewport. It uses the native `loading='lazy'` attribute or Intersection Observer API. Component lazy loading (code splitting) defers downloading and parsing the JavaScript code for a component until it's needed. Both reduce initial page load, but component lazy loading is more impactful for JavaScript-heavy apps since it reduces parse/execute time, while image lazy loading primarily saves bandwidth.

Should I lazy load the LCP (Largest Contentful Paint) image?▼

Never lazy load the LCP image. Lazy loading delays image download, which directly increases LCP. The LCP element should have `loading='eager'` (the default) and `fetchpriority='high'` to prioritize it. Only lazy-load images that start below the fold — images visible on initial load should always load eagerly. A common mistake is bulk-applying `loading='lazy'` to all images, which breaks the hero/banner LCP.

What is the Intersection Observer API and how does it work for lazy loading?▼

IntersectionObserver is a browser API that fires a callback when a target element enters or exits the viewport. For lazy loading, you set `data-src` on images instead of `src`, observe all such images, and swap `data-src` to `src` when the image enters the viewport. Unlike scroll event listeners, IntersectionObserver is asynchronous, doesn't block the main thread, and fires off the main thread. The native `loading='lazy'` attribute uses a similar mechanism internally and is preferred for simple cases.

How do I implement lazy loading in React without third-party libraries?▼

React 18+ supports lazy loading with `React.lazy(() => import('./Component'))` for component code splitting, wrapped in `<Suspense fallback={<Loading />}>`. For images, use a custom hook with IntersectionObserver or the native `loading='lazy'` HTML attribute. For virtualized long lists (thousands of items), use `@tanstack/react-virtual` or `react-window` to only render visible rows — this is more effective than lazy loading for list performance.

Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Related Articles

Apr 2, 2026 · 25 min read

Performance Optimization 2026: Complete Guide to Frontend, Backend, and Infrastructure

Comprehensive performance optimization guide for 2026 covering Core Web Vitals, JavaScript bundle optimization, server-side rendering, caching strategies, database query optimization, CDN configuration, and real-world benchmarks.

Apr 2, 2026 · 23 min read

Web Performance and Core Web Vitals 2026: Complete Optimization Guide

Complete guide to Core Web Vitals optimization in 2026. Covers LCP, INP, CLS, TTFB optimization with real code examples. Includes Next.js, React, Vite, Cloudflare Workers, and backend optimization strategies for achieving 90+ Lighthouse scores.

Apr 2, 2026 · 13 min read

Performance Testing in 2026: Beyond Page Load Times

A practical guide to performance testing in 2026 — Core Web Vitals, load testing, stress testing, soak testing, and building a performance culture that treats speed as a feature. Includes tooling recommendations and actionable measurement strategies.

Apr 2, 2026 · 14 min read

React Hooks Deep Dive: useEffect, useMemo, useCallback Performance Guide

Complete guide to React hooks performance in 2025. Master useEffect lifecycle patterns, useMemo vs useCallback differences, and when each hook helps or hurts performance.

Apr 2, 2026 · 11 min read

SVG Optimization Techniques: Reduce File Size by 70% Without Quality Loss

Step-by-step SVG optimization guide. Use SVGO, manual techniques, and build tool integration to cut SVG file sizes by 50-80% while preserving visual quality.

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →