Published on 29.6.2019
As React applications grow in size, the need to optimize for performance becomes even more critical. One of the best techniques for improving your app’s load time and reducing the initial bundle size is code splitting. This allows you to load only the parts of your app that are needed at a given time, reducing the amount of JavaScript that needs to be downloaded initially.
In this post, we’ll explore how to implement code splitting in React using React.lazy
and Suspense
. These two tools, introduced in React 16.6, enable you to dynamically load components as they’re needed, making your application more efficient and faster to load.
What Is Code Splitting and Why Is It Important?
Code splitting is the practice of breaking up your application’s code into smaller bundles that can be loaded on demand. Instead of loading the entire application upfront, code splitting allows you to only load the parts of the application the user actually needs, when they need them.
For instance, you can split off components or routes that aren’t immediately necessary, and only load them when the user interacts with them. This can drastically improve the performance of your application, especially when combined with techniques like lazy loading.
React.lazy: Lazy-Loading Components
React provides React.lazy
as a built-in function that allows you to define components that will be loaded dynamically. When using React.lazy
, React will only load the component when it’s needed, instead of including it in the initial bundle.
Here’s how React.lazy
works:
import React, { Suspense } from 'react';
// Lazy-load the component
const MyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<div>
<h1>Welcome to my app!</h1>
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
</div>
);
}
export default App;
In this example, MyComponent
is loaded lazily. When MyComponent
is needed (when it’s rendered), React will load the component dynamically. If the component is still loading, the Suspense
component will show a loading indicator (defined by the fallback
prop) until the component is ready.
How Does React.lazy
Work?
React.lazy
accepts a function that dynamically imports the component. The import returns a promise, and React takes care of loading the component asynchronously.
Here’s an example where we lazy-load multiple components:
const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));
const Contact = React.lazy(() => import('./Contact'));
With this setup, React will only load each component when it’s rendered, reducing the size of the initial bundle and improving performance.
Suspense: Handling the Loading State
React.lazy
on its own doesn’t provide a way to handle the loading state of the dynamically loaded components. That’s where Suspense
comes in. Suspense
is a wrapper component that handles the loading state for lazy-loaded components and provides a fallback UI while the component is being fetched.
The fallback
prop of Suspense
accepts any JSX element, typically a loading spinner or message:
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
This fallback
content is displayed while the lazily-loaded component is fetching its content. Once the component is loaded, React will render the component in place of the fallback UI.
Lazy-Loading Routes with React Router
One of the most common use cases for code splitting in React is lazy-loading routes. If your app has multiple pages or views, you can use React.lazy
and Suspense
to load each route only when the user navigates to it. This ensures that the initial bundle size remains small, and only the components necessary for the current route are loaded.
Let’s see how to set up lazy-loading with React Router
:
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } 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>}>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
</Suspense>
</Router>
);
}
export default App;
Here’s what’s happening in the example:
- We define our components (
Home
,About
,Contact
) usingReact.lazy
, which means they’ll be loaded only when needed. - We wrap our entire
Switch
andRoute
components withSuspense
, which ensures that while any of the components are being loaded, the fallback UI (e.g., a loading message) will be displayed. - When the user navigates to different routes, only the components required for that route are loaded dynamically.
This approach ensures that the initial bundle remains small, and only the code necessary for the current route is loaded.
When to Use Code Splitting with React.lazy
and Suspense
While React.lazy
and Suspense
are powerful tools for optimizing your app’s performance, they shouldn’t be overused. Here are some guidelines on when to use them:
- Large Components or Views: If your application has large, complex components or views that are not needed on the initial render, you should consider lazy-loading them.
- Routes: Lazy-loading routes with
React Router
is one of the most common and effective use cases forReact.lazy
andSuspense
. - Third-Party Libraries: If you’re using large third-party libraries that are not essential immediately, consider lazy-loading them to improve your app’s load time.
However, avoid using React.lazy
for small or simple components that won’t significantly impact the performance of your app. Overuse of lazy loading can also lead to “fragmentation” of your app, making it harder to manage and potentially introducing race conditions or layout shifts.
Limitations and Considerations
While React.lazy
and Suspense
offer significant performance improvements, there are some considerations to keep in mind:
- Suspense for Data Fetching: At the time of writing (React 16.6),
Suspense
only works for lazy-loaded components. If you want to handle data fetching withSuspense
, you need to use the experimentalSuspense
for data fetching, which isn’t fully stable yet. - Server-Side Rendering (SSR):
React.lazy
works with client-side rendering, but it requires additional configuration to work with server-side rendering. There are libraries likeloadable-components
that handle SSR better for code splitting. - Error Boundaries: When using lazy loading, it’s recommended to also implement error boundaries to handle loading errors gracefully. If a lazy-loaded component fails to load, your app can crash if it’s not caught properly.
Conclusion
Code splitting is a powerful technique for optimizing your React applications. By using React.lazy
and Suspense
, you can load components dynamically, reducing the size of the initial bundle and improving your app’s performance. Whether you’re lazy-loading routes or large components, this approach ensures that only the necessary code is loaded, making your app faster and more efficient.
Remember to use these tools wisely—lazy loading works best for large components or pages that aren’t needed right away. Don’t overdo it for smaller, less impactful components, and always consider performance profiling before implementing code splitting across your app.
By incorporating code splitting into your React app, you’ll be able to deliver a smoother user experience with faster load times, ultimately making your app more scalable and user-friendly.
Happy coding!