Introduction
By default, bundlers like Webpack or Vite combine your entire application into one large JavaScript bundle, which the browser must download before it can render anything. Code splitting breaks this single bundle into smaller chunks that are loaded on demand, so users only download the code needed for the page they're currently viewing. React supports this natively through React.lazy for lazily loading components and the Suspense component for showing a fallback UI while the chunk is being fetched.
Cricket analogy: Like a broadcaster who used to force viewers to download the entire day's match footage before watching, but now streams only the over you're currently watching, with a buffering graphic (Suspense) shown while that clip loads.
Syntax
import React, { Suspense, lazy } from 'react';
// Instead of a static import, use React.lazy with a dynamic import()
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
function App({ page }) {
return (
<Suspense fallback={<div>Loading page...</div>}>
{page === 'dashboard' ? <Dashboard /> : <Settings />}
</Suspense>
);
}Explanation
React.lazy takes a function that must call a dynamic import() and return a Promise resolving to a module with a default export containing a React component. The bundler recognizes the dynamic import() syntax and automatically splits that component into its own chunk file. Because loading a chunk over the network is asynchronous, React needs to know what to render while waiting — that's what Suspense is for. Any lazy component must be rendered inside a Suspense boundary with a fallback prop, which specifies the UI (like a spinner or skeleton) to show until the lazy component's code has finished downloading and can render.
Cricket analogy: Like sending a scout to fetch a specific substitute player's file only when the captain calls for them (dynamic import), with the twelfth-man board (fallback) displayed on the big screen until that player's details arrive.
A very common real-world use case is route-based code splitting: lazily loading each page/route component so that visiting the homepage doesn't force users to download the code for the admin panel, settings page, or other routes they may never visit.
Example
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Suspense, lazy } from 'react';
const Home = lazy(() => import('./pages/Home'));
const Profile = lazy(() => import('./pages/Profile'));
function AppRoutes() {
return (
<BrowserRouter>
<Suspense fallback={<p>Loading page...</p>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}Output
When the user first loads the app, only the code for the main bundle and the router are downloaded. Navigating to '/' briefly shows 'Loading page...' while the Home.chunk.js file downloads, then renders the Home page. Navigating to '/profile' triggers a separate network request for Profile's chunk, which was never downloaded until the user actually visited that route, reducing the app's initial load time.
Cricket analogy: Like a fan's app only downloading the live scorecard widget on open, then fetching the Home highlights reel only when tapped (briefly showing 'Loading highlights...'), and separately fetching Player Profile stats only when that tab is opened, keeping launch fast.
Key Takeaways
- Code splitting breaks a single large bundle into smaller chunks loaded on demand, improving initial load performance.
- React.lazy(() => import('./Component')) dynamically imports a component and defines a separate chunk for it.
- Every lazy-loaded component must be rendered inside a Suspense boundary with a fallback UI.
- Route-based code splitting (lazy-loading each page) is the most common and impactful pattern.
- React.lazy only supports default exports; named exports require a small wrapper module.
Practice what you learned
1. What must the function passed to React.lazy return?
2. What is required to render a component created with React.lazy?
3. What is the main performance benefit of route-based code splitting?
4. Which import style does React.lazy rely on to enable bundlers to split code into separate chunks?
Was this page helpful?
You May Also Like
React Performance Optimization
Learn how to prevent unnecessary re-renders in React using React.memo, useMemo, and useCallback.
React Router Basics
Learn how to add client-side routing to a React app using React Router v6's Routes, Route, Link, and NavLink components.
Dynamic Routing in React
Use URL parameters and the useParams and useNavigate hooks to build dynamic, data-driven routes in React Router.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics