Introduction
As React applications grow in size, the initial bundle can become large, leading to slower page loads and decreased user experience. One of the most effective ways to improve performance is route-based code splitting, which loads only the code necessary for the current route.
React provides built-in tools like React.lazy and Suspense that make lazy loading components straightforward.
In this article, we will cover:
- Benefits of code splitting for performance.
- How to use
React.lazyandSuspensewith React Router routes. - Practical examples of lazy loading page components.
- Combining lazy loading with nested routes for large applications.
Benefits of Route-Based Code Splitting
- Faster Initial Load
- Only the code needed for the initial route is downloaded.
- Reduced Bundle Size
- Large components and rarely used pages are split into separate chunks.
- Improved User Experience
- Faster page loads and responsive navigation.
- Better Performance on Mobile Networks
- Lazy loading reduces the data transferred, improving load times for slow connections.
- Scalable Architecture
- Applications remain maintainable as code grows, with clear separation between route chunks.
Using React.lazy for Lazy Loading
React.lazy allows components to be loaded dynamically only when they are rendered.
Syntax
const SomeComponent = React.lazy(() => import('./SomeComponent'));
- Returns a lazy-loaded component.
- Must be rendered inside a
Suspensecomponent that provides a fallback while loading.
Using Suspense with Lazy-Loaded Components
Suspense allows you to show a loading indicator while a lazy-loaded component is being fetched.
Example
import React, { Suspense } from "react";
const Home = React.lazy(() => import("./Home"));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Home />
</Suspense>
);
}
export default App;
fallbackcan be any React element, such as a spinner or placeholder text.- Improves user experience while waiting for the component to load.
Lazy Loading with React Router
Lazy loading works seamlessly with React Router to split code by route.
Example: Basic Route-Based Lazy Loading
import React, { Suspense } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
const Home = React.lazy(() => import("./Home"));
const About = React.lazy(() => import("./About"));
const Contact = React.lazy(() => import("./Contact"));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</Router>
);
}
export default App;
Explanation
- Each page component (
Home,About,Contact) is loaded only when its route is visited. - The initial bundle is smaller, improving performance.
Example: Lazy Loading Page Components
Step 1: Create Page Components
// Home.js
export default function Home() {
return <h1>Home Page</h1>;
}
// About.js
export default function About() {
return <h1>About Page</h1>;
}
// Contact.js
export default function Contact() {
return <h1>Contact Page</h1>;
}
Step 2: Lazy Load in Routes
import React, { Suspense } from "react";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
const Home = React.lazy(() => import("./Home"));
const About = React.lazy(() => import("./About"));
const Contact = React.lazy(() => import("./Contact"));
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link> |
<Link to="/about">About</Link> |
<Link to="/contact">Contact</Link>
</nav>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</Router>
);
}
export default App;
Explanation
- Users see the loading fallback while the component code is being fetched.
- Components not needed for the current route are not loaded initially, improving speed.
Combining Lazy Loading with Nested Routes
For larger apps, nested routes can also be lazy-loaded to optimize performance further.
Example: Nested Routes
import React, { Suspense } from "react";
import { BrowserRouter as Router, Routes, Route, Outlet, Link } from "react-router-dom";
const Dashboard = React.lazy(() => import("./Dashboard"));
const Profile = React.lazy(() => import("./Profile"));
const Settings = React.lazy(() => import("./Settings"));
function DashboardLayout() {
return (
<div>
<h1>Dashboard</h1>
<nav>
<Link to="profile">Profile</Link> |
<Link to="settings">Settings</Link>
</nav>
<Suspense fallback={<div>Loading Section...</div>}>
<Outlet />
</Suspense>
</div>
);
}
function App() {
return (
<Router>
<Suspense fallback={<div>Loading Page...</div>}>
<Routes>
<Route path="/" element={<div>Home Page</div>} />
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<Dashboard />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</Suspense>
</Router>
);
}
export default App;
Explanation
DashboardLayoutcontains a nestedOutletfor child routes.- Each child (
Profile,Settings) is lazy-loaded only when visited. - Nested lazy loading further reduces initial bundle size.
Benefits of Nested Lazy Loading
- Fine-Grained Performance Control
- Only the necessary chunks are loaded, even within a parent route.
- Faster Navigation Between Sections
- Lazy-loaded sections reduce the amount of code executed per route.
- Better Scalability
- Large applications with many pages or dashboard sections benefit the most.
Handling Loading States Gracefully
Example: Custom Loading Spinner
function Loading() {
return (
<div style={{ textAlign: "center", marginTop: "50px" }}>
<p>Loading...</p>
<div className="spinner"></div>
</div>
);
}
<Suspense fallback={<Loading />}>
<Routes> ... </Routes>
</Suspense>
- Users get a better visual experience than a plain “Loading…” text.
- Can be combined with CSS animations for smooth effects.
Tips and Best Practices
- Split by Route, Not Component
- Focus on pages or sections, not every small component.
- Use Meaningful Fallbacks
- Show loading indicators or skeleton UI to improve UX.
- Combine with Error Boundaries
- Catch lazy loading failures and display a fallback UI.
<Suspense fallback={<Loading />}>
<ErrorBoundary>
<Routes> ... </Routes>
</ErrorBoundary>
</Suspense>
- Avoid Over-Lazy Loading Tiny Components
- Lazy loading very small components can increase overhead.
- Prefetching Important Routes
- Use dynamic import with
webpackPrefetchto load critical pages in advance.
- Use dynamic import with
const About = React.lazy(() => import(/* webpackPrefetch: true */ './About'));
Leave a Reply