
Lazy Loading Done Right: The Definitive Guide to Images, Components & Routes
A practical breakdown of every lazy loading technique available in modern React and Next.js—intersection observer, dynamic imports, route-based splitting, and native browser lazy—with performance benchmarks to show what actually moves the needle.
The Three Types of Lazy Loading
Lazy loading means deferring the loading of resources until they're actually needed. In web development, this applies to three distinct areas: images (network bytes), JavaScript components (parse/compile time), and routes (entire page bundles). Each has different techniques and tradeoffs.
Image Lazy Loading
The native loading=lazy attribute is supported in all modern browsers and should be your default for below-fold images:
<!-- Always eager-load above-fold images -->
<img src="hero.jpg" alt="Hero" loading="eager" />
<!-- Lazy-load everything else -->
<img src="product.jpg" alt="Product" loading="lazy" width="400" height="300" />
Critical mistake to avoid: lazy-loading your LCP image. The browser can't start loading it until it's visible, which kills your LCP score. Always use loading=eager (or just omit the attribute) for your hero image.
Also always provide width and height attributes — without them, the browser can't reserve space and you'll get layout shift (CLS) when images load.
React Component Lazy Loading
Use React.lazy() with Suspense to split heavy components into separate chunks:
import { lazy, Suspense, useState } from 'react';
// These create separate JS chunks, loaded on demand
const RichTextEditor = lazy(() => import('./RichTextEditor'));
const DataChart = lazy(() => import('./DataChart'));
const VideoPlayer = lazy(() => import('./VideoPlayer'));
function ArticleEditor({ showChart }) {
const [editorVisible, setEditorVisible] = useState(false);
return (
<div>
<button onClick={() => setEditorVisible(true)}>
Open Editor
</button>
{editorVisible && (
<Suspense fallback={<div className="skeleton h-64" />}>
<RichTextEditor />
</Suspense>
)}
{showChart && (
<Suspense fallback={<div className="skeleton h-48" />}>
<DataChart />
</Suspense>
)}
</div>
);
}
Preloading on Hover
You can start loading a component before the user clicks by triggering the import on hover — this eliminates perceived loading time:
const Modal = lazy(() => import('./HeavyModal'));
// Preload the chunk when user hovers over the button
const preloadModal = () => import('./HeavyModal');
function App() {
const [open, setOpen] = useState(false);
return (
<>
<button
onMouseEnter={preloadModal} // preload on hover
onClick={() => setOpen(true)}
>
Open Settings
</button>
{open && (
<Suspense fallback={null}>
<Modal onClose={() => setOpen(false)} />
</Suspense>
)}
</>
);
}
IntersectionObserver for Scroll-Based Loading
For content below the fold, use IntersectionObserver to load when approaching the viewport:
import { useEffect, useRef, useState } from 'react';
function LazySection({ children }) {
const ref = useRef();
const [visible, setVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setVisible(true);
observer.disconnect();
}
},
{ rootMargin: '200px' } // start loading 200px before visible
);
observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return <div ref={ref}>{visible ? children : <Skeleton />}</div>;
}
Common Mistakes
- Lazy-loading above-fold images (kills LCP)
- Missing width/height on images (causes CLS)
- No Suspense boundary around lazy components (crashes)
- Lazy-loading tiny components — only worth it above ~20kb
Applied correctly, lazy loading is one of the few optimizations that simultaneously improves LCP, FID/INP, and bandwidth usage.
Get weekly highlights
No spam, unsubscribe anytime.
Ranked.ai
AI-powered SEO & PPC service — fully managed, white hat, and built for modern search engines. Starting at $99/month.
LittleBird
AI-powered deep research & outreach automation — find leads, analyze markets, and write personalized emails at scale.



Comments (0)
Sign in to comment
No comments yet. Be the first to comment!