React.lazy enables code splitting — dynamically importing components only when they're needed. Instead of loading all JavaScript upfront, you load components on demand, reducing the initial bundle size and improving load time.
How it works:
React.lazy(() => import("./Component")) creates a lazy-loaded component<Suspense> for a fallbackBasic usage:
1import { lazy, Suspense } from "react";23const Dashboard = lazy(() => import("./Dashboard"));45function App() {6 return (7 <Suspense fallback={<div>Loading dashboard...</div>}>8 <Dashboard />9 </Suspense>10 );11}
Named exports: If the module uses named exports, map the default export:
1const Dashboard = lazy(() =>2 import("./Dashboard").then(module => ({3 default: module.Dashboard,4 }))5);
Route-based code splitting — the most common pattern:
1import { lazy } from "react";2import { BrowserRouter, Routes, Route, Suspense } from "react-router-dom";34const Home = lazy(() => import("./pages/Home"));5const About = lazy(() => import("./pages/About"));6const Dashboard = lazy(() => import("./pages/Dashboard"));7const Settings = lazy(() => import("./pages/Settings"));89function PageLoader() {10 return (11 <div className="page-loader">12 <div className="spinner" />13 <p>Loading page...</p>14 </div>15 );16}1718function App() {19 return (20 <BrowserRouter>21 <Suspense fallback={<PageLoader />}>22 <Routes>23 <Route path="/" element={<Home />} />24 <Route path="/about" element={<About />} />25 <Route path="/dashboard" element={<Dashboard />} />26 <Route path="/settings" element={<Settings />} />27 </Routes>28 </Suspense>29 </BrowserRouter>30 );31}
Error handling with ErrorBoundary:
1const Dashboard = lazy(() => import("./Dashboard"));23function App() {4 return (5 <ErrorBoundary fallback={<p>Failed to load page</p>}>6 <Suspense fallback={<Loading />}>7 <Dashboard />8 </Suspense>9 </ErrorBoundary>10 );11}
Benefits:
Pitfalls:
link rel="preload" or import() trick for critical resources