React Suspense & Lazy Loading
Written By: Avinash Malhotra
Updated on
As React apps grow, you may not want to load all components and code at once — it can make your app slow, especially for large projects.
React Suspense and Lazy Loading solve this by allowing you to load components only when needed, improving performance and reducing initial load time.
What is Lazy Loading?
Lazy Loading means loading parts of your app only when they are required — instead of loading everything upfront. This helps improve performance by reducing initial load time.
For example:
- Load the dashboard component only after the user logs in.
- Load a chart component only when the user opens the analytics page.
React provides a function called React.lazy() to make this easy.
import React, { Suspense } from "react";
// Lazy import
const About = React.lazy(() => import("./About"));
function App() {
return (
<h1>React Lazy Loading Example</h1>
{/* Suspense wraps lazy components */}
<Suspense fallback={<p>Loading About Page...</p>}>
<About />
</Suspense>
);
}
export default App;
How it works:
- React.lazy(): This function takes a function that returns a dynamic import (a promise) and returns a React component that loads the module when rendered.
- Suspense: This component wraps around lazy-loaded components and provides a fallback UI (like a loading spinner or message) while the component is being loaded.
In this example, the About component is only loaded when it is rendered inside the Suspense component. Until then, the fallback UI ("Loading About Page...") is displayed.
Understanding React Suspense
React Suspense is a component that lets you "wait" for something before rendering. It's designed to handle asynchronous operations like code splitting, data fetching, and more.
Suspense works by:
- Wrapping components that might "suspend" (throw a promise during rendering)
- Showing a fallback UI while waiting
- Rendering the component once the promise resolves
While Suspense is commonly used with React.lazy() for code splitting, it can also be used with other async patterns like data fetching libraries that support Suspense.
Complete Example with Multiple Lazy Components
Here's a more comprehensive example showing lazy loading of multiple components with proper error handling:
import React, { Suspense, lazy } from "react";
import ErrorBoundary from "./ErrorBoundary";
// Lazy imports
const Home = lazy(() => import("./Home"));
const About = lazy(() => import("./About"));
const Contact = lazy(() => import("./Contact"));
function App() {
const [currentPage, setCurrentPage] = React.useState("home");
const renderPage = () => {
switch (currentPage) {
case "home":
return <Home />;
case "about":
return <About />;
case "contact":
return <Contact />;
default:
return <Home />;
}
};
return (
<div>
<nav>
<button onClick={() => setCurrentPage("home")}>Home</button>
<button onClick={() => setCurrentPage("about")}>About</button>
<button onClick={() => setCurrentPage("contact")}>Contact</button>
</nav>
<ErrorBoundary>
<Suspense fallback={<div>Loading page...</div>}>
{renderPage()}
</Suspense>
</ErrorBoundary>
</div>
);
}
export default App;
In this example:
- Each page component is loaded only when needed
- Suspense provides a loading fallback
- ErrorBoundary catches any loading errors
- Code splitting reduces the initial bundle size
Best Practices for Suspense and Lazy Loading
- Use meaningful fallbacks: Provide loading indicators that match your app's design and give users feedback.
- Combine with Error Boundaries: Always wrap Suspense in Error Boundaries to handle loading failures gracefully.
- Preload critical components: Use dynamic imports with preload hints for components that are likely to be needed soon.
- Avoid over-splitting: Don't split every small component; group related components to balance bundle sizes.
- Test loading states: Ensure your app handles slow networks and loading errors properly.
- Use React DevTools: Monitor bundle sizes and loading performance with React DevTools Profiler.
Limitations and Considerations
While powerful, Suspense and lazy loading have some limitations:
- Server-side rendering: Lazy loading doesn't work with SSR; components are loaded immediately.
- No server-side Suspense: Suspense for data fetching is experimental and not recommended for production.
- Bundle splitting: Requires proper build tool configuration (Webpack, Vite, etc.) for code splitting.
- Loading waterfalls: Nested Suspense can create loading waterfalls if not managed carefully.
For server-side rendering, consider alternatives like Loadable Components or React Loadable.