Code Splitting with lazy
Load components on demand to shrink the initial bundle.
Syntax
const Page = lazy(() => import('./Page.jsx'));Code splitting breaks your app into smaller chunks that load only when needed. lazy lets you import a component dynamically, and Suspense shows a fallback while its chunk downloads.
Where it helps
- Routes that most users never visit.
- Heavy components like charts or rich editors.
The result is a smaller initial download and a faster first paint.
Example
import { Suspense, lazy, useState } from 'react';
const Chart = lazy(() => import('./Chart.jsx'));
function Report() {
const [show, setShow] = useState(false);
return (
<div>
<button onClick={() => setShow(true)}>Show chart</button>
{show && (
<Suspense fallback={<p>Loading chart...</p>}>
<Chart />
</Suspense>
)}
</div>
);
}When to use it
- An admin panel lazy-loads the Reports section so users who only use the Dashboard never download the charting and export libraries.
- A React Router app wraps each route component in lazy() so each page's JavaScript downloads only when the user navigates to that route for the first time.
- A low-end device optimisation splits the rich text editor into a lazy chunk so the initial page load stays under 50 kB even though the editor is 200 kB.
More examples
Lazy route per page
Each route is lazy-imported so only the current page's JS chunk is downloaded on first visit.
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const Settings = lazy(() => import('./pages/Settings'));
const Reports = lazy(() => import('./pages/Reports'));
function App() {
return (
<Suspense fallback={<p>Loading page...</p>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/settings" element={<Settings />} />
<Route path="/reports" element={<Reports />} />
</Routes>
</Suspense>
);
}Lazy-load a heavy modal
Downloads the editor bundle only when the modal is opened, not when the page first loads.
import { lazy, Suspense, useState } from 'react';
const RichTextEditor = lazy(() => import('./RichTextEditor'));
function NewPostButton() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>New Post</button>
{open && (
<Suspense fallback={<div className="modal-skeleton" />}>
<RichTextEditor onClose={() => setOpen(false)} />
</Suspense>
)}
</>
);
}Prefetch chunk on hover
Triggers the dynamic import on hover so the chunk is already cached by the time the user clicks.
const ReportsPage = React.lazy(() => import('./ReportsPage'));
// Prefetch the chunk when the user hovers the link
function NavLink() {
const prefetch = () => import('./ReportsPage');
return (
<a href="/reports" onMouseEnter={prefetch}>
Reports
</a>
);
}
Discussion