Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Implement lazy loading for offscreen content

Images and heavy resources below the fold are lazy loaded to improve initial performance.

Utilities
Quick take
Typical fix time 15 min
  • Use loading='lazy' for images below the fold
  • Never lazy load LCP/above-fold images—they need priority
  • Lazy load iframes, videos, and heavy components
  • Use Intersection Observer for custom lazy loading
  • If fold position is unclear from the snippet, do not invent a lazy-loading defect
Why it matters: Lazy loading defers non-critical resources until needed—this reduces initial page weight, speeds up first paint, and saves bandwidth for content users may never scroll to.

Rule Details

Lazy loading is a prioritization tool, not a blanket rule. web.dev's performance guidance (opens in new tab) treats it as a way to defer genuinely offscreen work, and it pairs naturally with import-on-visibility when whole sections need JavaScript deferral.

Code Examples

Native Lazy Loading for Media

<!-- Good: below-the-fold image -->
<img
  src="product.jpg"
  alt="Product"
  width="400"
  height="300"
  loading="lazy"
>
 
<!-- Good: heavy embed kept off the critical path -->
<iframe
  src="https://www.youtube.com/embed/..."
  loading="lazy"
  width="560"
  height="315"
  title="Video"
></iframe>

Keep First-Viewport Media Eager

import Image from 'next/image'
 
function ProductGrid({ products }) {
  return (
    <div className="grid">
      {products.map((product, index) => (
        <Image
          key={product.id}
          src={product.image}
          alt={product.name}
          width={300}
          height={200}
          priority={index < 4}
        />
      ))}
    </div>
  )
}

Use Intersection Observer for Section-Level Deferral

import { useEffect, useRef, useState } from 'react'
 
function LazySection({ children, fallback, rootMargin = '300px' }) {
  const ref = useRef<HTMLDivElement | null>(null)
  const [isVisible, setIsVisible] = useState(false)
 
  useEffect(() => {
    const node = ref.current
    if (!node) return
 
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (!entry.isIntersecting) return
        setIsVisible(true)
        observer.disconnect()
      },
      { rootMargin }
    )
 
    observer.observe(node)
    return () => observer.disconnect()
  }, [rootMargin])
 
  return <div ref={ref}>{isVisible ? children : fallback}</div>
}

Why It Matters

  • Lower initial transfer: offscreen images, embeds, and iframes stop competing with CSS, fonts, and hero media.
  • Better resource ordering: the browser can spend its early bandwidth on current-route content instead of speculative content lower on the page.
  • Less wasted work: many users never scroll to the bottom of the route.
  • Easy to misuse: lazy-loading the wrong asset can make LCP worse instead of better, which is why Lighthouse (opens in new tab) and field measurements should confirm the change.

When to Lazy Load

ContentLazy load?Guidance
Hero image or likely LCP mediaNoLoad it immediately and give it high priority
Images clearly below the foldYesNative loading="lazy" is usually enough
Product grids and card feedsUsuallyExclude the first visible row or roughly the first 2-4 images
Heavy iframes and video embedsYesPrefer placeholders or facades plus lazy loading
Offscreen sections with expensive contentSometimesUse Intersection Observer only when native lazy loading is not enough

Native Lazy Loading vs Intersection Observer

  • Use native loading="lazy" for standard images and iframes.
  • Use Intersection Observer when you need custom placeholders, section-level deferral, or earlier loading before the user reaches the element.
  • Start with a rootMargin around 200px-400px; increase it only if fast scrolls outrun your placeholders.

Common Mistakes

  • Lazy-loading the LCP element: this is a direct regression for perceived load speed.
  • Marking every image lazy: large desktop viewports can make several images visible immediately.
  • Skipping dimensions or placeholders: deferred media must still reserve space to avoid CLS.
  • Using JavaScript for simple image lazy loading: native browser support should be the default choice.
  • Loading offscreen sections too late: users should not scroll into blank sections while the content catches up.

Support Notes

  • Native lazy-loading behavior varies by browser heuristics, so test the real scrolling and above-the-fold thresholds in the project target browsers.
  • Use a fallback note when a browser ignores the attribute or when a framework changes the loading strategy under the hood.

Verification

Validate the final scroll behavior in PageSpeed Insights (opens in new tab) or a throttled waterfall trace, because the goal is not just fewer initial requests but offscreen media that still appears ready when users reach it.

Automated Checks

  • Inspect the network waterfall and verify below-the-fold images and embeds start after critical CSS, fonts, and hero assets.
  • Check that deferred content reserves space with explicit dimensions or placeholders so CLS remains low.

Manual Checks

  • Confirm the LCP image and any clearly above-the-fold media do not use loading="lazy".
  • Scroll through the page on a throttled mobile profile and confirm sections are ready before users reach them.
  • If you add Intersection Observer, confirm it solves a real gap that native lazy loading could not handle cleanly.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Verify that images and other heavy resources below the fold are lazy loaded. Only report this when the code or route context makes offscreen placement clear.

Fix

Auto-fix issues

Implement lazy loading for images, videos, and iframes using native loading='lazy' or JavaScript solutions.

Explain

Learn more

Explain how lazy loading defers resource loading until needed, improving initial page load performance.

Review

Code review

Review the routes, assets, and loading behavior that affect Implement lazy loading for offscreen content. Flag exact files, requests, or rendering steps that add unnecessary network, CPU, or layout cost, and describe the measurement method used to confirm the issue.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

PageSpeed Insights
pagespeed.web.devTool

Rules that often go hand-in-hand with this one.

Prioritize loading critical images

Hero and above-the-fold images are preloaded with high fetch priority for LCP.

Images
Optimize largest contentful paint

The largest content element loads within 2.5 seconds for a good user experience.

Performance
Use resource hints for faster loading

Implement preload, prefetch, and preconnect hints to optimize resource loading priority.

Performance
Load non-critical code when content approaches the viewport

Use viewport-aware loading to fetch components, embeds, and feature code shortly before they become visible instead of shipping them on first load.

Performance

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385