Lazy loading is an optimization strategy that involves delaying the loading of resources until they actually become needed. Instead of loading everything at once during the first page render, we load elements "on demand" - when the user scrolls the page, navigates to a new route, or performs an action requiring a specific component.
1<!-- Native lazy loading (supported by modern browsers) -->
2<img src="hero-image.jpg" alt="Main image" loading="lazy" />
3
4<!-- Eager loading for above-the-fold images -->
5<img src="logo.jpg" alt="Logo" loading="eager" />
6
7<!-- Auto (default browser behavior) -->
8<img src="banner.jpg" alt="Banner" loading="auto" />1// Lazy loading implementation with Intersection Observer
2class LazyImageLoader {
3 constructor() {
4 this.imageObserver = new IntersectionObserver(
5 this.handleIntersection.bind(this),
6 {
7 root: null, // viewport
8 rootMargin: '50px', // load 50px before entering viewport
9 threshold: 0.1 // trigger when 10% of image is visible
10 }
11 );
12
13 this.initLazyImages();
14 }
15
16 initLazyImages() {
17 const lazyImages = document.querySelectorAll('img[data-src]');
18 lazyImages.forEach(img => {
19 this.imageObserver.observe(img);
20 });
21 }
22
23 handleIntersection(entries) {
24 entries.forEach(entry => {
25 if (entry.isIntersecting) {
26 const img = entry.target;
27 this.loadImage(img);
28 this.imageObserver.unobserve(img);
29 }
30 });
31 }
32
33 loadImage(img) {
34 // Load image from the data-src attribute
35 img.src = img.dataset.src;
36 img.classList.add('loaded');
37
38 // Remove data-src after loading
39 delete img.dataset.src;
40
41 // Error handling for loading
42 img.onerror = () => {
43 img.src = '/images/error-placeholder.png';
44 img.classList.add('error');
45 };
46 }
47}
48
49// Initialization
50document.addEventListener('DOMContentLoaded', () => {
51 new LazyImageLoader();
52});1// Hook for lazy loading images
2import { useState, useRef, useEffect } from 'react';
3
4function useIntersectionObserver(options = {}) {
5 const [isIntersecting, setIsIntersecting] = useState(false);
6 const [hasIntersected, setHasIntersected] = useState(false);
7 const elementRef = useRef(null);
8
9 useEffect(() => {
10 const element = elementRef.current;
11 if (!element) return;
12
13 const observer = new IntersectionObserver(
14 ([entry]) => {
15 const isElementIntersecting = entry.isIntersecting;
16 setIsIntersecting(isElementIntersecting);
17
18 if (isElementIntersecting && !hasIntersected) {
19 setHasIntersected(true);
20 }
21 },
22 { threshold: 0.1, rootMargin: '50px', ...options }
23 );
24
25 observer.observe(element);
26
27 return () => observer.disconnect();
28 }, [hasIntersected, options]);
29
30 return { elementRef, isIntersecting, hasIntersected };
31}
32
33// LazyImage component
34function LazyImage({ src, alt, placeholder, className = '' }) {
35 const [imageLoaded, setImageLoaded] = useState(false);
36 const [imageError, setImageError] = useState(false);
37 const { elementRef, hasIntersected } = useIntersectionObserver();
38
39 const handleImageLoad = () => {
40 setImageLoaded(true);
41 };
42
43 const handleImageError = () => {
44 setImageError(true);
45 };
46
47 return (
48 <div ref={elementRef} className={`lazy-image-container ${className}`}>
49 {/* Placeholder shown during loading */}
50 {!imageLoaded && (
51 <img
52 src={placeholder}
53 alt={`${alt} placeholder`}
54 className="placeholder-image"
55 />
56 )}
57
58 {/* Actual image loaded lazily */}
59 {hasIntersected && (
60 <img
61 src={imageError ? '/images/error.png' : src}
62 alt={alt}
63 onLoad={handleImageLoad}
64 onError={handleImageError}
65 className={`lazy-image ${imageLoaded ? 'loaded' : 'loading'}`}
66 />
67 )}
68
69 {/* Loading spinner */}
70 {hasIntersected && !imageLoaded && !imageError && (
71 <div className="image-spinner">Loading...</div>
72 )}
73 </div>
74 );
75}
76
77// Component usage
78function Gallery({ images }) {
79 return (
80 <div className="gallery">
81 {images.map((image, index) => (
82 <LazyImage
83 key={index}
84 src={image.url}
85 alt={image.alt}
86 placeholder={image.placeholder}
87 className="gallery-item"
88 />
89 ))}
90 </div>
91 );
92}1// Component with progressive loading (blur -> sharp)
2function ProgressiveImage({ src, placeholder, alt }) {
3 const [imageLoaded, setImageLoaded] = useState(false);
4 const { elementRef, hasIntersected } = useIntersectionObserver();
5
6 return (
7 <div ref={elementRef} className="progressive-image">
8 {/* Blurred placeholder */}
9 <img
10 src={placeholder}
11 alt={alt}
12 className={`progressive-placeholder`}
13 style={{
14 filter: 'blur(10px)',
15 transition: 'opacity 0.3s ease'
16 }}
17 />
18
19 {/* Sharp image */}
20 {hasIntersected && (
21 <img
22 src={src}
23 alt={alt}
24 onLoad={() => setImageLoaded(true)}
25 className={`main-image ${imageLoaded ? 'visible' : 'hidden'}`}
26 style={{
27 opacity: imageLoaded ? 1 : 0,
28 transition: 'opacity 0.3s ease'
29 }}
30 />
31 )}
32 </div>
33 );
34}1import { Suspense, lazy } from 'react';
2import { Routes, Route } from 'react-router-dom';
3import ErrorBoundary from './components/ErrorBoundary';
4import LoadingSpinner from './components/LoadingSpinner';
5
6// Lazy loading route components
7const HomePage = lazy(() => import('./pages/HomePage'));
8const ProductsPage = lazy(() => import('./pages/ProductsPage'));
9const ProductDetailPage = lazy(() => import('./pages/ProductDetailPage'));
10const UserProfilePage = lazy(() => import('./pages/UserProfilePage'));
11const AdminPanel = lazy(() => import('./pages/AdminPanel'));
12
13// Components with custom loading and error handling
14const LazyRoute = ({ children }) => (
15 <ErrorBoundary>
16 <Suspense fallback={<LoadingSpinner />}>
17 {children}
18 </Suspense>
19 </ErrorBoundary>
20);
21
22function App() {
23 return (
24 <Routes>
25 <Route path="/" element={<LazyRoute><HomePage /></LazyRoute>} />
26 <Route path="/products" element={<LazyRoute><ProductsPage /></LazyRoute>} />
27 <Route path="/products/:id" element={<LazyRoute><ProductDetailPage /></LazyRoute>} />
28 <Route path="/profile" element={<LazyRoute><UserProfilePage /></LazyRoute>} />
29 <Route path="/admin/*" element={<LazyRoute><AdminPanel /></LazyRoute>} />
30 </Routes>
31 );
32}1// Preloading routes based on user behavior
2class RoutePreloader {
3 constructor() {
4 this.preloadedRoutes = new Set();
5 this.setupPreloading();
6 }
7
8 setupPreloading() {
9 // Preload on hover
10 this.setupHoverPreloading();
11
12 // Preload based on probability
13 this.setupPredictivePreloading();
14
15 // Preload in idle time
16 this.setupIdlePreloading();
17 }
18
19 setupHoverPreloading() {
20 document.addEventListener('mouseover', (e) => {
21 const link = e.target.closest('a[href]');
22 if (link && this.shouldPreload(link.href)) {
23 this.preloadRoute(link.href);
24 }
25 });
26 }
27
28 setupPredictivePreloading() {
29 // If user is on /products, preload /products/:id
30 const currentPath = window.location.pathname;
31
32 if (currentPath === '/products') {
33 // Delay so as not to affect current page load
34 setTimeout(() => {
35 this.preloadRoute('/products/popular');
36 }, 2000);
37 }
38 }
39
40 setupIdlePreloading() {
41 if ('requestIdleCallback' in window) {
42 requestIdleCallback(() => {
43 this.preloadCriticalRoutes();
44 });
45 }
46 }
47
48 preloadRoute(path) {
49 if (this.preloadedRoutes.has(path)) return;
50
51 const routeMap = {
52 '/products': () => import('./pages/ProductsPage'),
53 '/profile': () => import('./pages/UserProfilePage'),
54 '/admin': () => import('./pages/AdminPanel')
55 };
56
57 const preloadFunction = routeMap[path];
58 if (preloadFunction) {
59 preloadFunction().then(() => {
60 this.preloadedRoutes.add(path);
61 console.log(`Route ${path} preloaded`);
62 });
63 }
64 }
65
66 shouldPreload(href) {
67 // Preload only internal links
68 return href.startsWith(window.location.origin);
69 }
70
71 preloadCriticalRoutes() {
72 const critical = ['/products', '/profile'];
73 critical.forEach(route => this.preloadRoute(route));
74 }
75}
76
77// Initialization
78new RoutePreloader();1// Lazy loading expensive components
2const ExpensiveChart = lazy(() => import('./components/ExpensiveChart'));
3const DataVisualization = lazy(() => import('./components/DataVisualization'));
4const VideoPlayer = lazy(() => import('./components/VideoPlayer'));
5const CodeEditor = lazy(() => import('./components/CodeEditor'));
6
7function Dashboard({ activeTab, data }) {
8 return (
9 <div className="dashboard">
10 <nav className="dashboard-nav">
11 {/* Navigation always visible */}
12 </nav>
13
14 <main className="dashboard-content">
15 {activeTab === 'charts' && (
16 <Suspense fallback={<div>Loading charts...</div>}>
17 <ExpensiveChart data={data} />
18 </Suspense>
19 )}
20
21 {activeTab === 'visualization' && (
22 <Suspense fallback={<div>Loading visualization...</div>}>
23 <DataVisualization data={data} />
24 </Suspense>
25 )}
26
27 {activeTab === 'video' && (
28 <Suspense fallback={<div>Loading player...</div>}>
29 <VideoPlayer />
30 </Suspense>
31 )}
32
33 {activeTab === 'editor' && (
34 <Suspense fallback={<div>Loading editor...</div>}>
35 <CodeEditor />
36 </Suspense>
37 )}
38 </main>
39 </div>
40 );
41}1// Hook for lazy modals
2function useLazyModal() {
3 const [isOpen, setIsOpen] = useState(false);
4 const [Component, setComponent] = useState(null);
5
6 const openModal = async (modalType) => {
7 const modalMap = {
8 'user-settings': () => import('./modals/UserSettingsModal'),
9 'payment': () => import('./modals/PaymentModal'),
10 'image-editor': () => import('./modals/ImageEditorModal'),
11 };
12
13 const importFunction = modalMap[modalType];
14 if (importFunction) {
15 const { default: ModalComponent } = await importFunction();
16 setComponent(() => ModalComponent);
17 setIsOpen(true);
18 }
19 };
20
21 const closeModal = () => {
22 setIsOpen(false);
23 // Optionally: unload component after closing
24 setTimeout(() => setComponent(null), 300);
25 };
26
27 return { Component, isOpen, openModal, closeModal };
28}
29
30// Using lazy modals
31function App() {
32 const { Component: ModalComponent, isOpen, openModal, closeModal } = useLazyModal();
33
34 return (
35 <div>
36 <button onClick={() => openModal('user-settings')}>
37 User Settings
38 </button>
39 <button onClick={() => openModal('payment')}>
40 Payment
41 </button>
42 <button onClick={() => openModal('image-editor')}>
43 Image Editor
44 </button>
45
46 {isOpen && ModalComponent && (
47 <Suspense fallback={<div>Loading...</div>}>
48 <ModalComponent onClose={closeModal} />
49 </Suspense>
50 )}
51 </div>
52 );
53}1// Lazy loading entire features
2const AdminFeature = lazy(() => import('./features/Admin'));
3const AnalyticsFeature = lazy(() => import('./features/Analytics'));
4const ShoppingCartFeature = lazy(() => import('./features/ShoppingCart'));
5
6function FeatureLoader({ feature, ...props }) {
7 const featureMap = {
8 admin: AdminFeature,
9 analytics: AnalyticsFeature,
10 cart: ShoppingCartFeature
11 };
12
13 const FeatureComponent = featureMap[feature];
14
15 if (!FeatureComponent) {
16 return <div>Unknown feature: {feature}</div>;
17 }
18
19 return (
20 <ErrorBoundary>
21 <Suspense fallback={<FeatureLoadingSkeleton feature={feature} />}>
22 <FeatureComponent {...props} />
23 </Suspense>
24 </ErrorBoundary>
25 );
26}
27
28// Skeleton for different features
29function FeatureLoadingSkeleton({ feature }) {
30 const skeletons = {
31 admin: <AdminSkeleton />,
32 analytics: <ChartsSkeleton />,
33 cart: <CartSkeleton />
34 };
35
36 return skeletons[feature] || <DefaultSkeleton />;
37}1// Virtual scrolling for large lists
2import { FixedSizeList as List } from 'react-window';
3
4function VirtualizedList({ items, itemHeight = 50 }) {
5 const [loadedItems, setLoadedItems] = useState(new Set());
6
7 const loadItem = useCallback(async (index) => {
8 if (loadedItems.has(index)) return;
9
10 // Simulate data loading
11 await new Promise(resolve => setTimeout(resolve, 100));
12 setLoadedItems(prev => new Set([...prev, index]));
13 }, [loadedItems]);
14
15 const Row = ({ index, style }) => {
16 const item = items[index];
17 const isLoaded = loadedItems.has(index);
18
19 useEffect(() => {
20 if (!isLoaded) {
21 loadItem(index);
22 }
23 }, [index, isLoaded, loadItem]);
24
25 return (
26 <div style={style} className="list-item">
27 {isLoaded ? (
28 <ItemContent item={item} />
29 ) : (
30 <ItemSkeleton />
31 )}
32 </div>
33 );
34 };
35
36 return (
37 <List
38 height={400}
39 itemCount={items.length}
40 itemSize={itemHeight}
41 overscanCount={5} // Preload 5 items beyond viewport
42 >
43 {Row}
44 </List>
45 );
46}1// Cache for lazy-loaded components
2class ComponentCache {
3 constructor() {
4 this.cache = new Map();
5 }
6
7 async get(key, loader) {
8 if (this.cache.has(key)) {
9 return this.cache.get(key);
10 }
11
12 const component = await loader();
13 this.cache.set(key, component);
14 return component;
15 }
16
17 preload(key, loader) {
18 if (!this.cache.has(key)) {
19 this.get(key, loader);
20 }
21 }
22
23 clear(key) {
24 if (key) {
25 this.cache.delete(key);
26 } else {
27 this.cache.clear();
28 }
29 }
30}
31
32const componentCache = new ComponentCache();
33
34// Hook utilizing cache
35function useLazyComponent(componentKey, loader) {
36 const [Component, setComponent] = useState(null);
37 const [loading, setLoading] = useState(false);
38 const [error, setError] = useState(null);
39
40 const loadComponent = useCallback(async () => {
41 if (Component) return;
42
43 setLoading(true);
44 setError(null);
45
46 try {
47 const loadedComponent = await componentCache.get(componentKey, loader);
48 setComponent(() => loadedComponent.default);
49 } catch (err) {
50 setError(err);
51 } finally {
52 setLoading(false);
53 }
54 }, [componentKey, loader, Component]);
55
56 // Preload function
57 const preload = useCallback(() => {
58 componentCache.preload(componentKey, loader);
59 }, [componentKey, loader]);
60
61 return { Component, loading, error, loadComponent, preload };
62}1// Adaptive loading based on connection
2function useConnectionAwareLazyLoading() {
3 const [connectionType, setConnectionType] = useState('4g');
4
5 useEffect(() => {
6 const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
7
8 if (connection) {
9 setConnectionType(connection.effectiveType);
10
11 const updateConnection = () => {
12 setConnectionType(connection.effectiveType);
13 };
14
15 connection.addEventListener('change', updateConnection);
16 return () => connection.removeEventListener('change', updateConnection);
17 }
18 }, []);
19
20 const shouldLazyLoad = useCallback((priority = 'normal') => {
21 const strategies = {
22 '4g': { immediate: true, normal: true, low: true },
23 '3g': { immediate: true, normal: true, low: false },
24 '2g': { immediate: true, normal: false, low: false },
25 'slow-2g': { immediate: false, normal: false, low: false }
26 };
27
28 return strategies[connectionType]?.[priority] ?? false;
29 }, [connectionType]);
30
31 const getLoadingStrategy = useCallback(() => {
32 const strategies = {
33 '4g': 'aggressive', // Load everything
34 '3g': 'moderate', // Load only when needed
35 '2g': 'conservative', // Load minimally
36 'slow-2g': 'minimal' // Only essentials
37 };
38
39 return strategies[connectionType] || 'moderate';
40 }, [connectionType]);
41
42 return { connectionType, shouldLazyLoad, getLoadingStrategy };
43}
44
45// Component adapting to connection
46function AdaptiveImageGrid({ images }) {
47 const { shouldLazyLoad, getLoadingStrategy } = useConnectionAwareLazyLoading();
48 const strategy = getLoadingStrategy();
49
50 const imageSettings = {
51 aggressive: { quality: 'high', eager: 10 },
52 moderate: { quality: 'medium', eager: 4 },
53 conservative: { quality: 'low', eager: 2 },
54 minimal: { quality: 'thumbnail', eager: 1 }
55 };
56
57 const settings = imageSettings[strategy];
58
59 return (
60 <div className="image-grid">
61 {images.map((image, index) => (
62 <AdaptiveImage
63 key={image.id}
64 src={image.url}
65 quality={settings.quality}
66 placeholder={image.placeholder}
67 loading={index < settings.eager ? 'eager' : 'lazy'}
68 shouldOptimize={strategy !== 'aggressive'}
69 />
70 ))}
71 </div>
72 );
73}1// Lazy loading performance monitoring
2class LazyLoadingMonitor {
3 constructor() {
4 this.metrics = {
5 componentsLoaded: 0,
6 totalLoadTime: 0,
7 failedLoads: 0,
8 cacheHits: 0
9 };
10
11 this.startTime = performance.now();
12 }
13
14 trackComponentLoad(componentName, loadTime, fromCache = false) {
15 this.metrics.componentsLoaded++;
16 this.metrics.totalLoadTime += loadTime;
17
18 if (fromCache) {
19 this.metrics.cacheHits++;
20 }
21
22 // Web Vitals tracking
23 if (loadTime > 2500) { // Slow loading threshold
24 console.warn(`Slow lazy load detected: ${componentName} took ${loadTime}ms`);
25 }
26
27 // Send to analytics
28 this.sendMetrics('component_lazy_load', {
29 component: componentName,
30 loadTime,
31 fromCache,
32 timestamp: Date.now()
33 });
34 }
35
36 trackFailedLoad(componentName, error) {
37 this.metrics.failedLoads++;
38
39 console.error(`Failed to lazy load ${componentName}:`, error);
40
41 this.sendMetrics('component_lazy_load_error', {
42 component: componentName,
43 error: error.message,
44 timestamp: Date.now()
45 });
46 }
47
48 getAverageLoadTime() {
49 return this.metrics.componentsLoaded > 0
50 ? this.metrics.totalLoadTime / this.metrics.componentsLoaded
51 : 0;
52 }
53
54 getCacheHitRate() {
55 return this.metrics.componentsLoaded > 0
56 ? (this.metrics.cacheHits / this.metrics.componentsLoaded) * 100
57 : 0;
58 }
59
60 sendMetrics(event, data) {
61 // Integration with Google Analytics, Mixpanel, etc.
62 if (typeof gtag !== 'undefined') {
63 gtag('event', event, data);
64 }
65 }
66
67 generateReport() {
68 const sessionTime = performance.now() - this.startTime;
69
70 return {
71 sessionDuration: sessionTime,
72 componentsLoaded: this.metrics.componentsLoaded,
73 averageLoadTime: this.getAverageLoadTime(),
74 cacheHitRate: this.getCacheHitRate(),
75 failureRate: (this.metrics.failedLoads / this.metrics.componentsLoaded) * 100,
76 totalFailures: this.metrics.failedLoads
77 };
78 }
79}
80
81// Singleton instance
82const lazyLoadMonitor = new LazyLoadingMonitor();
83
84// Hook with monitoring
85function useMonitoredLazyComponent(componentName, loader) {
86 const [Component, setComponent] = useState(null);
87 const [loading, setLoading] = useState(false);
88
89 const loadComponent = useCallback(async () => {
90 if (Component) return;
91
92 const startTime = performance.now();
93 setLoading(true);
94
95 try {
96 const loadedComponent = await loader();
97 const loadTime = performance.now() - startTime;
98
99 setComponent(() => loadedComponent.default);
100 lazyLoadMonitor.trackComponentLoad(componentName, loadTime);
101 } catch (error) {
102 lazyLoadMonitor.trackFailedLoad(componentName, error);
103 throw error;
104 } finally {
105 setLoading(false);
106 }
107 }, [componentName, loader, Component]);
108
109 return { Component, loading, loadComponent };
110}1// Above-the-fold content - eager loading
2// Below-the-fold content - lazy loading
3function ContentStrategy() {
4 return (
5 <>
6 {/* Critical path - eager */}
7 <Header />
8 <Hero />
9 <MainContent />
10
11 {/* Secondary content - lazy */}
12 <Suspense fallback={<SectionSkeleton />}>
13 <LazySection name="testimonials" />
14 </Suspense>
15
16 <Suspense fallback={<SectionSkeleton />}>
17 <LazySection name="newsletter" />
18 </Suspense>
19
20 {/* Low priority - very lazy */}
21 <Suspense fallback={<FooterSkeleton />}>
22 <LazyFooter />
23 </Suspense>
24 </>
25 );
26}1// Always provide graceful fallbacks
2function LazyComponentWrapper({ children, fallback, errorFallback }) {
3 return (
4 <ErrorBoundary
5 fallback={errorFallback || <div>An error occurred during loading</div>}
6 >
7 <Suspense fallback={fallback || <LoadingSkeleton />}>
8 {children}
9 </Suspense>
10 </ErrorBoundary>
11 );
12}1// Strategic preloading based on user behavior
2function useStrategicPreloading() {
3 useEffect(() => {
4 // Preload after 2 seconds of inactivity
5 const timeoutId = setTimeout(() => {
6 // Preload likely next components
7 import('./components/UserProfile');
8 import('./components/ShoppingCart');
9 }, 2000);
10
11 // Preload based on mouse movement toward a link
12 const handleMouseMove = (e) => {
13 // If cursor is approaching a specific element
14 // preload the associated component
15 };
16
17 document.addEventListener('mousemove', handleMouseMove);
18
19 return () => {
20 clearTimeout(timeoutId);
21 document.removeEventListener('mousemove', handleMouseMove);
22 };
23 }, []);
24}Lazy loading is a powerful optimization technique that:
The key to success is balancing performance with user experience - lazy loading should be invisible to the user while significantly improving application performance.