The ultimate guide to React Lazy Loading
Lazy loading is a powerful way to improve the performance of your site.
Why Lazy Load?
Lazy loading your react components can improve performance, reduce bundle size, and enhance user experience.
Instead of loading everything at once, you may want to consider lazy loading components that are not immediately visible.
Strategy #1: Prefetch Lazy load
One strategy for lazy loading is the prefetch strategy. As soon as the main component loads, start lazy loading the given module automatically, even if it’s not going to be visible.
Do this if you have a strong reason to believe that the user will always need this component but you don’t want to affect the main page load.
import(/* webpackPrefetch: true */ './navbar.tsx') // Prefetch lazy load
Use case:
Let’s say your site has a navigation menu like so:
Observations:
- The navigation menu is not shown to the user until they click the hamburger menu, so it’s a great candidate to lazy load.
- This item is highly likely to be used at some point by every user, so we should lazy load this navbar as soon as the main page has been loaded.
Strategy #2: Standard/Route-based lazy loading
Starts loading the module as soon as the route or component with the lazy loaded component is triggered.
- Note: Often requires a loading indicator.
Example without Lazy Loading:
Let’s say we have a Dashboard
component that loads when the user goes to www.frontendmonitoring.com/dashboard.
// NO LAZY LOADING! :(
import * as React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import Dashboard from './pages/Dashboard'; // <-- Terrible! Dashboard is loaded immediately when app loads
const App = () => {
return (
<>
<h1>React Router</h1>
<nav>
<Link to="/">Home</Link>
<Link to="/dashboard">Dashboard</Link>
</nav>
<Routes>
<Route index element={<Home />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="*" element={<NoMatch />} />
</Routes>
</>
);
};
const NoMatch = () => {
return <p>There's nothing here: 404!</p>;
};
export default App;
- without lazy loading, the dashboard will be loaded even before the user goes to that page. This means that the entire app will be slower, because it is loading unused code.
Let’s add lazy loading!
// WITH LAZY LOADING! :(
import * as React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
// import Dashboard from './pages/Dashboard'; // <-- Terrible! Dashboard is loaded immediately when app loads
const Dashboard = React.lazy(() => import('./pages/Dashboard')) // <-- With Lazy loading!
const App = () => {
return (
<>
<h1>React Router</h1>
<nav>
<Link to="/">Home</Link>
<Link to="/dashboard">Dashboard</Link>
</nav>
<Routes>
<Route index element={<Home />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="*" element={<NoMatch />} />
</Routes>
</>
);
};
const NoMatch = () => {
return <p>There's nothing here: 404!</p>;
};
export default App;
Strategy #3: Lazy Loading triggered by a hover or an action.
Best of both worlds, because it loads the necessary component first, and only starts lazy loading when the user performs an action such as hovering or focusing on a specific element.
- can be used to load components when a user hovers on a link or over a button.
For example:
Let’s say we have a heavy-duty Globe component that loads when the user clicks the button “Open Globe”.
- we can’t be sure that the user will always open the globe, so we don’t want to prefetch it.
- we don’t want to show a loading indicator for too long, so the standard method #2 isn’t ideal.
So what can we do?
Let’s load when the user hovers over the “Open Globe” button, because that indicates that the user is intending/considering to open it!
const loadGlobe = () => {
return import('../Globe');
}
const Globe = React.lazy(loadGlobe);
function App() {
// on hover, call loadGlobe
return (
<button onMouseEnter={loadGlobe}>Open Globe</button>
)
}