Image Lazy Loading
Image Lazy Loading
Defer loading of images until they enter the viewport.
Native Lazy Loading
// Simple native lazy loading
<img
src="/image.jpg"
alt="Description"
loading="lazy"
width={800}
height={600}
/>
// With responsive images
<img
src="/image.webp"
srcset="/image-400.webp 400w, /image-800.webp 800w"
sizes="(max-width: 768px) 100vw, 50vw"
loading="lazy"
width={800}
height={600}
alt="Description"
/>
Intersection Observer Implementation
// hooks/useLazyImage.js
function useLazyImage(src, options = {}) {
const [imageSrc, setImageSrc] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const imgRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setImageSrc(src);
observer.disconnect();
}
});
},
{ rootMargin: options.rootMargin || '100px' }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, [src, options.rootMargin]);
const handleLoad = () => setIsLoaded(true);
return { imgRef, imageSrc, isLoaded, handleLoad };
}
// Component
function LazyImage({ src, alt, ...props }) {
const { imgRef, imageSrc, isLoaded, handleLoad } = useLazyImage(src);
return (
<div ref={imgRef} className="lazy-image-container">
{imageSrc ? (
<img
src={imageSrc}
alt={alt}
onLoad={handleLoad}
className={isLoaded ? 'loaded' : 'loading'}
{...props}
/>
) : (
<div className="image-placeholder" />
)}
</div>
);
}
Blur-Up Technique
function BlurUpImage({ src, placeholder, alt }) {
const [loaded, setLoaded] = useState(false);
return (
<div className="blur-up-container">
<img
src={placeholder}
alt={alt}
className="blur-up-placeholder"
style={{ filter: loaded ? 'none' : 'blur(20px)' }}
/>
{loaded && (
<img
src={src}
alt={alt}
className="blur-up-full"
onLoad={() => setLoaded(true)}
/>
)}
</div>
);
}
Component Lazy Loading
Component Lazy Loading
Load components on demand to reduce initial bundle.
React.lazy Implementation
import { lazy, Suspense } from 'react';
// Lazy load heavy components
const Chart = lazy(() => import('./Chart'));
const DataGrid = lazy(() => import('./DataGrid'));
const RichTextEditor = lazy(() => import('./RichTextEditor'));
function Dashboard() {
const [activeTab, setActiveTab] = useState('overview');
return (
<div>
<TabList>
<Tab onClick={() => setActiveTab('overview')}>Overview</Tab>
<Tab onClick={() => setActiveTab('charts')}>Charts</Tab>
<Tab onClick={() => setActiveTab('data')}>Data</Tab>
</TabList>
<Suspense fallback={<TabLoader />}>
{activeTab === 'overview' && <OverviewTab />}
{activeTab === 'charts' && <Chart data={chartData} />}
{activeTab === 'data' && <DataGrid rows={tableData} />}
</Suspense>
</div>
);
}
On-Interaction Loading
function ExpandableSection({ title, loadChildren }) {
const [isExpanded, setIsExpanded] = useState(false);
const [Component, setComponent] = useState(null);
const handleExpand = async () => {
if (!Component && loadChildren) {
const mod = await loadChildren();
setComponent(() => mod.default);
}
setIsExpanded(!isExpanded);
};
return (
<div>
<button onClick={handleExpand}>
{isExpanded ? '▼' : '▶'} {title}
</button>
{isExpanded && Component && (
<div className="expandable-content">
<Component />
</div>
)}
</div>
);
}
// Usage
<ExpandableSection
title="Advanced Settings"
loadChildren={() => import('./AdvancedSettings')}
/>
Conditional Loading
function App({ user }) {
const [AdminPanel, setAdminPanel] = useState(null);
useEffect(() => {
if (user?.role === 'admin' && !AdminPanel) {
import('./AdminPanel').then(module => {
setAdminPanel(() => module.default);
});
}
}, [user, AdminPanel]);
return (
<div>
<Header />
<main>
<Routes>{/* ... */}</Routes>
</main>
{AdminPanel && <AdminPanel />}
</div>
);
}
Route Lazy Loading
Route Lazy Loading
Load route components only when navigating to them.
React Router Implementation
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Products = lazy(() => import('./pages/Products'));
const ProductDetail = lazy(() => import('./pages/ProductDetail'));
const Cart = lazy(() => import('./pages/Cart'));
const Checkout = lazy(() => import('./pages/Checkout'));
const PageLoader = () => (
<div className="page-loader">
<Spinner />
<p>Loading page...</p>
</div>
);
function App() {
return (
<BrowserRouter>
<Layout>
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/cart" element={<Cart />} />
<Route path="/checkout" element={<Checkout />} />
</Routes>
</Suspense>
</Layout>
</BrowserRouter>
);
}
Prefetching on Hover
import { Link } from 'react-router-dom';
function NavLink({ to, children }) {
const prefetch = () => {
// Map routes to their chunks
const routes = {
'/about': () => import('./pages/About'),
'/products': () => import('./pages/Products'),
'/cart': () => import('./pages/Cart'),
};
if (routes[to]) {
routes[to]();
}
};
return (
<Link to={to} onMouseEnter={prefetch}>
{children}
</Link>
);
}
Nested Routes
const DashboardLayout = lazy(() => import('./layouts/DashboardLayout'));
const Overview = lazy(() => import('./pages/dashboard/Overview'));
const Analytics = lazy(() => import('./pages/dashboard/Analytics'));
const Settings = lazy(() => import('./pages/dashboard/Settings'));
function App() {
return (
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<Overview />} />
<Route path="analytics" element={<Analytics />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</Suspense>
);
}
Benefits of Route Lazy Loading
- Faster initial load: Only load code for current page
- Better caching: Smaller chunks cache better
- Reduced memory: Unused code isn't loaded
- Better UX: Loading indicators for navigation
Practice Problems
Create a reusable React component implementing Lazy Loading. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Lazy Loading using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Lazy Loading for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What is native lazy loading?
2. When should you lazy load a component?
3. What is the blur-up technique?
4. Why prefetch routes on hover?
5. What should you show while a lazy component loads?
Flashcards
Question
What is native lazy loading?
Click to reveal answer
Answer
A browser feature that defers loading images until they enter the viewport using loading="lazy".
Question
How do you lazy load a React component?
Click to reveal answer
Answer
Use React.lazy() with dynamic import and wrap in Suspense with a fallback.
Question
What is the blur-up technique?
Click to reveal answer
Answer
Showing a small, blurred placeholder image while the full image loads.
Question
Why prefetch routes?
Click to reveal answer
Answer
To preload route code before navigation, making page transitions faster.
Question
What is Lazy Loading?
Click to reveal answer
Answer
Lazy Loading is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.Use native loading="lazy" for images for zero-JS solution
- 2.React.lazy with Suspense enables component-level lazy loading
- 3.Route-based splitting loads only code for the current page
- 4.Prefetch on hover for instant-feeling navigation
- 5.Always show loading states for lazy loaded content
Interview Tips
- •Explain the difference between native and JavaScript lazy loading
- •Discuss when to use component vs route lazy loading
- •Know how to handle loading and error states
Cheat Sheet
Lazy Loading Cheat Sheet
Native Image Lazy Loading
<img src="image.jpg" loading="lazy" width={800} height={600} />
Component Lazy Loading
const Component = lazy(() => import('./Component'));
<Suspense fallback={<Spinner />}>
<Component />
</Suspense>
Route Lazy Loading
const Page = lazy(() => import('./pages/Page'));
Prefetching
onMouseEnter={() => import('./Component')}