Published on 23.9.2020
In modern web development, performance is key. Users expect apps to load quickly and run smoothly. One effective way to enhance performance in React applications is by implementing code splitting and lazy loading. These techniques help reduce initial loading times by only loading the parts of your app that are needed right away, and deferring the loading of other parts until necessary.
In this guide, we’ll show you how to optimize React apps using React.lazy
and Suspense
for component-based code splitting, how to implement dynamic imports in React Router, and explore best practices for keeping bundle sizes small.
Using React.lazy and Suspense for Component-Based Code Splitting
Code splitting is the process of breaking your application into smaller, more manageable chunks. With React, React.lazy and Suspense are the primary tools for implementing component-based code splitting.
What is React.lazy?
React.lazy
allows you to dynamically import components only when they are needed, instead of including them in the initial bundle. When the component is requested (e.g., when it’s rendered), React will fetch the component asynchronously.
Here’s how you can use React.lazy to dynamically load a component:
import React, { Suspense } from 'react';
// Lazy load the component
const MyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<div>
<h1>Hello, React!</h1>
{/* Wrap lazy-loaded component with Suspense */}
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
</div>
);
}
export default App;
In this example:
- The
MyComponent
is dynamically imported usingReact.lazy
. Suspense
is used to display a fallback (e.g., a loading spinner or message) while the component is being loaded asynchronously.
How Does It Work?
- When the app starts, React will only load the main bundle (initially).
- When the
<MyComponent />
is rendered for the first time, it is fetched in the background, allowing the rest of the app to remain functional while the component is loading. - Once the component is fetched, it is cached for subsequent renders, so it won’t need to be fetched again.
Suspense Fallback:
The fallback
prop of the Suspense
component is essential, as it provides a visual indicator that the lazy-loaded component is being fetched.
Implementing Dynamic Imports in React Router
When using React Router, it’s common to implement code splitting for different routes. This can significantly reduce the initial bundle size by only loading the components needed for the current route.
To implement dynamic imports in React Router, we can use React.lazy
in combination with React Router’s Route components:
Example with React Router
First, install React Router if you haven’t already:
npm install react-router-dom
Then, set up code-split routes using React.lazy
:
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
// Lazy load route components
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="/home" component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Switch>
</Suspense>
</Router>
);
}
export default App;
Explanation:
- Each route component (
Home
,About
,Contact
) is dynamically imported usingReact.lazy
. Suspense
wraps the entire route rendering logic, and you can specify a fallback (like a loading spinner) to show while the route component is being loaded.- React Router’s
Switch
ensures that only the matching route is rendered.
Best Practices for Keeping Bundle Sizes Small
While code splitting and lazy loading are powerful tools, it’s important to follow some best practices to ensure your app stays optimized.
1. Minimize the Use of Large Third-Party Libraries
- Avoid importing entire libraries if only a small part is required.
- For example, instead of importing the entire Lodash library, you can import only the specific utility function you need:
import debounce from 'lodash/debounce'; // Import only the debounce function
- Consider alternatives to large libraries, or look for lightweight versions (e.g.,
lodash-es
for ES modules).
2. Split Critical Libraries into Separate Bundles
- Split third-party libraries from your application code to ensure that React and other heavy libraries are loaded separately.
- Use dynamic imports for third-party libraries you don’t need initially, such as analytics or UI frameworks.
const analytics = React.lazy(() => import('analytics-lib'));
3. Split Vendor Code and Application Code
React and React Router can easily be separated into their own vendor chunk, so users don’t need to re-download React code every time they visit a new page. You can achieve this by configuring webpack to create multiple entry points.
// Webpack config
optimization: {
splitChunks: {
chunks: 'all',
},
},
4. Use Preloading for Critical Routes
For critical pages or routes, you can preload components before they are needed. This can be helpful for routes that you know will be visited next (like a user profile page after login).
// Preload a route component when the app is idle
const Profile = React.lazy(() => import('./Profile'));
Profile.preload();
5. Leverage Tree Shaking
Tree shaking is a feature in bundlers like webpack that eliminates unused code from your final bundle. Make sure your bundler is properly configured to take advantage of tree shaking.
- For example, using ES6 module imports will allow webpack to eliminate unused exports.
// Use ES6 imports for better tree shaking
import { specificFunction } from 'some-library';
Conclusion
Implementing code splitting and lazy loading in your React applications can have a significant impact on performance, reducing load times and enhancing user experience. By using React.lazy
and Suspense
, along with dynamic imports in React Router, you can efficiently manage what gets loaded and when. Follow best practices to further optimize your app by reducing bundle sizes, splitting critical libraries, and using tree shaking.
Incorporating these techniques into your React workflow will ensure your app remains fast, scalable, and user-friendly.