A guide to using React suspense and lazy loading components
Analyse your bundle & identify large components
We know that the bundle size is large, but how do we know which components to blame? webpack-bundle-analyzer is the perfect tool to investigate that. When using create-react-app, you can add this scripts to your package.json:
// generate build including bundle-stats.json npm run build -- --stats // analyse bundle-stats.json webpack-bundle-analyzer ./build/bundle-stats.json
We see that some packages take up a lot of space. They will get loaded and parsed before the application mounts in the DOM, but it’s possible they’re not needed on the page we visit. It would be nice to separate them into their own bundle, and load them only when needed.
Code splitting in React can be achieved by something called ‘dynamic imports’ and
React.lazy, which looks something like this:
import * as React from 'react' import Users from './Users' const Posts = React.lazy(() => import('./Posts')) // rest of component file
Below you see the result of bundle splitting in the bundle shown earlier in this post (screenshots from the componentizer app):
How does this work?
To be clear about what happens when using React.lazy we’re going to look at webpack’s code splitting, one of its more powerful features. It is used to split up code in various bundles to reduce bundle sizes and load times. The most convenient way to achieve it is by using dynamic imports are inline function calls from within modules/components. An inline function call looks like this:
const User = import('./User') // Results in 1.chunk.js const User = import(/* webpackChunkName: 'User' */ './User') // Results in User.chunk.js
In React, just importing components like that will not work. This is where
React.lazy comes in. This is a function that let's you use your dynamic imports as a regular components.
const User = React.lazy(() => import('./User'))
Sometimes we know (or can try to predict) if a component is going to be used. In that case we want to use prefetch or preload, depending on the situation. You can indicate this to Webpack by using ‘magic comments’.
import(/* webpackPrefetch: true */ './myComponent') // The above line results in a link tag appended to the <head> <link rel="prefetch" as="script" href="/static/js/2.chunk.js">
There are two options here:
- webPackPreload: Indicating that the browser will need this resource soon after loading, using link preloading. This can be used at for example fonts, important images or scripts that are needed on the current page.
- webPackPrefetch: uses browser idle time to load resources it might need in the future, using link prefetching. This is used for fetching resources and put them in cache, so when they are used on the next page for example, they load instantly.
Components imported using
React.lazy must be wrapped inside a Suspense tag. This is a feature of React that defers rendering until the lazy loaded component is fetched. Suspense needs to be placed anywhere above the lazy loaded component in the component tree. It provides a fallback for when a component inside it hasn’t loaded yet, using the required ‘fallback’ prop. The beautiful thing about Suspense is that it doesn’t have to be inside the component where something is imported, but can also be higher up in the component tree. In the example you see how Suspense works when this is the case:
Things to look forward to
In the current stable release of React (17), Suspense is limited to use with
React.lazy. This a nice feature for now, but what’s coming is even more exiting: loading data with Suspense! This means we can use Suspense with fetching JSON or images. Having more control over loading states when data fetched will result in better loading states in UI’s.
To learn more about Suspense and what’s coming to react in the future, watch this amazing talk by Dan Abramov: https://www.youtube.com/watch?v=nLF0n9SACd4.