Skip to main content

    Lazy Loading

    Lazy loading defers offscreen assets to improve initial load, but misusing it can hurt LCP or delay content discovery.

    Definition

    Lazy loading defers loading of offscreen assets (images/iframes) such as loading="lazy". It reduces initial payload, but lazy-loading the main hero image can hurt LCP. If important content appears only after interaction/scroll, indexing can become less reliable.

    Why it matters

    • Reduces initial payload and improves initial page load speed
    • Saves bandwidth — especially friendly for mobile users
    • Helps avoid CLS when paired with reserved dimensions
    • Native browser support via loading="lazy" — no extra JS needed
    • Especially effective for long pages with many images
    • Misuse can hurt LCP and indexability (don't lazy-load hero images)
    • Lazy iframes (like YouTube embeds) significantly reduce initial blocking

    How to implement

    • Don't lazy-load hero images — use fetchpriority="high" or <link rel="preload">
    • Apply loading="lazy" consistently to below-the-fold images
    • Ensure important content isn't hidden behind scroll/interaction (affects crawlers)
    • Always set width and height on lazy images to avoid CLS
    • Iframes (YouTube embeds) also support loading="lazy"
    • Use Intersection Observer for advanced control (animations, etc.)
    • Test: verify in DevTools Network panel that images load only when needed

    Examples

    html
    <!-- Below-fold images use lazy -->
    <img src="/below-fold.webp" alt="Product image" loading="lazy" width="800" height="600" />
    
    <!-- Hero image uses eager (default) + fetchpriority -->
    <img src="/hero.webp" alt="Main visual" loading="eager" fetchpriority="high" />
    
    <!-- Iframes can also be lazy -->
    <iframe src="https://www.youtube.com/embed/xxx" loading="lazy"></iframe>
    html
    // Fine-grained control over lazy loading
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          observer.unobserve(img);
        }
      });
    }, { rootMargin: '100px' });
    
    document.querySelectorAll('img[data-src]').forEach(img => {
      observer.observe(img);
    });

    Related

    FAQ

    Common questions about this term.

    Back to glossary