Lazy loading is deferred loading of components on demand (on first use), reducing initial bundle size and improving load time.
How it works step-by-step:
React.lazy() takes a function that returns a dynamic import().Suspense wraps the lazy component and shows a fallback while loading.React.lazy + Suspense:
1import { lazy, Suspense } from "react";23// Lazy load heavy components4const HeavyChart = lazy(() => import("./HeavyChart"));5const AdminPanel = lazy(() => import("./AdminPanel"));6const MarkdownEditor = lazy(() => import("./MarkdownEditor"));7const SettingsPage = lazy(() => import("./SettingsPage"));89function App() {10 return (11 <Suspense fallback={<div className="animate-pulse">Loading...</div>}>12 <Routes>13 <Route path="/" element={<Home />} />14 <Route path="/chart" element={<HeavyChart />} />15 <Route path="/admin" element={<AdminPanel />} />16 <Route path="/editor" element={<MarkdownEditor />} />17 <Route path="/settings" element={<SettingsPage />} />18 </Routes>19 </Suspense>20 );21}
With React Router (route-based splitting):
1const router = createBrowserRouter([2 {3 path: "/",4 lazy: async () => {5 const { Home } = await import("./pages/Home");6 return { Component: Home };7 }8 },9 {10 path: "/dashboard",11 lazy: async () => {12 const { Dashboard } = await import("./pages/Dashboard");13 return { Component: Dashboard };14 }15 },16 {17 path: "/settings",18 lazy: async () => {19 const { Settings } = await import("./pages/Settings");20 return { Component: Settings };21 }22 }23]);
Error handling with ErrorBoundary:
1import { ErrorBoundary } from "react-error-boundary";23const LazyComponent = lazy(() => import("./Component"));45<ErrorBoundary6 fallback={<div>Failed to load component. <button onClick={() => window.location.reload()}>Retry</button></div>}7 onReset={() => window.location.reload()}8>9 <Suspense fallback={<Spinner />}>10 <LazyComponent />11 </Suspense>12</ErrorBoundary>
Error handling for lazy loading failures:
1// Custom hook for lazy loading with error handling2function useLazyComponent(factory: () => Promise<{ default: React.ComponentType }>) {3 const [Component, setComponent] = useState<React.ComponentType | null>(null);4 const [error, setError] = useState<Error | null>(null);56 useEffect(() => {7 factory()8 .then(mod => setComponent(() => mod.default))9 .catch(err => setError(err));10 }, [factory]);1112 return { Component, error, isLoading: !Component && !error };13}
Configuration options:
Performance considerations:
source-map-explorer or Next.js bundle analyzer to find heavy components.1// Preload on hover2<Link3 to="/dashboard"4 onMouseEnter={() => import("./pages/Dashboard")}5>6 Dashboard7</Link>
Rule: Lazy-load heavy components (editors, charts, modals, admin panels). DON'T lazy-load small components — the overhead of the dynamic import and Suspense boundary exceeds the bundle savings.