Implement lazy loading for offscreen content
Images and heavy resources below the fold are lazy loaded to improve initial performance.
- 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
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
| Content | Lazy load? | Guidance |
|---|---|---|
| Hero image or likely LCP media | No | Load it immediately and give it high priority |
| Images clearly below the fold | Yes | Native loading="lazy" is usually enough |
| Product grids and card feeds | Usually | Exclude the first visible row or roughly the first 2-4 images |
| Heavy iframes and video embeds | Yes | Prefer placeholders or facades plus lazy loading |
| Offscreen sections with expensive content | Sometimes | Use 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
rootMarginaround200px-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.