Suspense with Lazy-Loaded Routes in React
In React, Suspense and React.lazy are powerful tools that enable code splitting and lazy loading for components, including routes. By using Suspense with lazy-loaded routes, you can improve your application’s performance by loading routes and their components only when needed, instead of loading everything upfront. This is particularly useful for large applications with many routes or heavy components.
What is React.lazy
?
React.lazy
is a function that enables dynamic import of a component, meaning the component is only loaded when it’s actually needed. It’s a way to split your code and load it in chunks, which can lead to faster initial loading times and more efficient resource use.
What is Suspense
?
Suspense
is a component that you can wrap around code that is being lazy-loaded. It allows you to specify a fallback UI (like a loading spinner or message) while the component is being loaded.
Using Suspense with Lazy-Loaded Routes
In React Router, you can combine Suspense
and React.lazy
to lazy-load routes, which means the components associated with those routes will only be loaded when the route is visited, not before.
Here’s how to set up Suspense with lazy-loaded routes:
1. Basic Setup of React.lazy with Suspense
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
// Lazy load components
const HomePage = lazy(() => import('./HomePage'));
const AboutPage = lazy(() => import('./AboutPage'));
const ContactPage = lazy(() => import('./ContactPage'));
const App = () => {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
{/* Suspense wrapper for lazy-loaded routes */}
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<ContactPage />} />
</Routes>
</Suspense>
</Router>
);
};
export default App;
Explanation:
React.lazy
: We useReact.lazy
to import theHomePage
,AboutPage
, andContactPage
components. These components will be loaded only when the user navigates to their respective routes.Suspense
: We wrap theRoutes
in aSuspense
component to handle the fallback UI while the component is being loaded.- The
fallback
prop is a placeholder UI (e.g., a loading spinner or message) that will be displayed until the lazy-loaded component is ready.
- The
2. Lazy-Loading with Nested Routes and Suspense
If you have a more complex app with nested routes, you can still use Suspense
with lazy-loaded components. This is particularly useful when you want to lazy-load large sections or modules of your application.
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
// Lazy load components
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
const Settings = lazy(() => import('./Settings'));
const App = () => {
return (
<Router>
<nav>
<Link to="/dashboard">Dashboard</Link>
</nav>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</Suspense>
</Router>
);
};
export default App;
Explanation:
- Nested Routes: The
/dashboard
route has nested routes (/dashboard/profile
and/dashboard/settings
) that are lazy-loaded as well. - Suspense: The entire route configuration is wrapped in a
Suspense
component, ensuring that all lazy-loaded components, including nested ones, have a fallback UI while loading.
3. Lazy-Loading and Error Boundaries
In real-world applications, lazy-loaded components might fail to load due to network issues or other reasons. You can handle such errors with Error Boundaries along with Suspense
.
Adding Error Boundaries:
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
// Lazy load components
const HomePage = lazy(() => import('./HomePage'));
const AboutPage = lazy(() => import('./AboutPage'));
const ErrorBoundary = ({ children }) => {
return (
<React.ErrorBoundary fallback={<div>Something went wrong!</div>}>
{children}
</React.ErrorBoundary>
);
};
const App = () => {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
{/* Error Boundary and Suspense */}
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</Suspense>
</ErrorBoundary>
</Router>
);
};
export default App;
Explanation:
- Error Boundaries: The
ErrorBoundary
component is used to catch any errors that occur while loading the lazy-loaded components and display a fallback UI. - Suspense:
Suspense
still handles the loading state, whileErrorBoundary
ensures that any errors are caught and handled gracefully.
4. Using Suspense for Route-based Data Fetching
Another common pattern is to use Suspense
for data fetching when routes are loaded. React’s Suspense can be used alongside a data-fetching library (such as React Query
, Apollo Client
, or custom fetchers) to manage loading states.
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
// Lazy load components
const PostList = lazy(() => import('./PostList'));
const App = () => {
return (
<Router>
<nav>
<Link to="/posts">Posts</Link>
</nav>
<Suspense fallback={<div>Loading posts...</div>}>
<Routes>
<Route path="/posts" element={<PostList />} />
</Routes>
</Suspense>
</Router>
);
};
export default App;
In this example, PostList
is a component that fetches data for posts. The Suspense component will show a loading state while the component fetches the data, and then display the data once it’s available.
Performance Benefits
- Faster Initial Load: By lazily loading routes and components, the browser doesn’t have to load everything upfront, leading to faster initial render times.
- Reduced Bundle Size: Lazy loading breaks the JavaScript bundle into smaller pieces, reducing the amount of JavaScript that needs to be downloaded initially.
- On-demand Loading: Code for routes and components is only fetched when needed, optimizing performance, especially for large applications.