Skip to main content
CodeRocket
Performancehighloading

Implement lazy loading for offscreen content

rule · lazy-loading

Lazy loading is a prioritization tool, not a blanket rule. web.dev's performance guidance (opens in a 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

HTML
<!-- 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

TSX
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

TSX
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 a 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 a 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.