Modern React 34: Code splitting and lazy loading
This is part 34 of the Modern React development series.
A React app does not need to send every component to the browser before the first screen can work. Code splitting lets the app load some code later, and lazy connects that delayed code to Suspense.
Concept
lazy declares a component whose code is loaded the first time React tries to render it. While the loading Promise is pending, the component suspends and the nearest Suspense boundary shows its fallback.
Terms
- Code splitting: Breaking a JavaScript bundle into chunks that can load separately.
- Lazy loading: Loading code or assets only when they are needed.
- Dynamic import: The
import()syntax that returns a Promise for a module. - Chunk: A piece of bundled JavaScript emitted by the build tool.
Mental model
Think of lazy code as a room behind a closed door. The first time the user goes there, React asks the bundler for the room’s code and Suspense covers the wait.
How it is used
Use lazy loading for routes, admin panels, charts, editors, modals with heavy dependencies, rarely used settings, and any component that pulls a large library not needed on initial view.
How to use it
- Declare
lazycomponents outside rendering functions. - Return a dynamic import that resolves to a default component export.
- Wrap the lazy component in a Suspense boundary.
- Use route-level code splitting when navigation is the natural boundary.
- Preload or prefetch expensive chunks when the user is likely to need them soon.
Example: Lazy chart
import { lazy, Suspense } from "react";
const RevenueChart = lazy(() => import("./RevenueChart"));
export function AnalyticsPanel() { return ( <Suspense fallback={<p>Loading chart...</p>}> <RevenueChart /> </Suspense> );}Loading chart...
The chart code is requested when the panel renders, and Suspense owns the loading UI.
Example: Lazy route table
import { lazy } from "react";
const AdminUsersPage = lazy(() => import("./routes/AdminUsersPage"));const PublicHomePage = lazy(() => import("./routes/PublicHomePage"));
export const routes = [ { path: "/", element: <PublicHomePage /> }, { path: "/admin/users", element: <AdminUsersPage /> },];const AdminUsersPage = lazy(() => import("./routes/AdminUsersPage"));
const PublicHomePage = lazy(() => import("./routes/PublicHomePage"));
export const routes = [
{ path: "/", element: <PublicHomePage /> },
{ path: "/admin/users", element: <AdminUsersPage /> },
];
Route boundaries are natural split points because users only need the code for the route they visit.
Details to watch
- Declaration location: Declare lazy components outside other components so React does not reset them every render.
- Default export:
lazyexpects the dynamic import to resolve to a module with adefaultcomponent. - Fallback quality: Use a fallback that fits the region’s layout to avoid jarring jumps.
- Over-splitting: Too many tiny chunks can add network overhead. Split at meaningful boundaries.
Series navigation
- Previous: Part 33: Error boundaries and recovery
- Next: Part 35: Styling, design tokens, and variants
- Series index: Modern React development