Lazy Loading and code splitting
Last updated
Last updated
The new release of React 16.6 rolled in with some new features that can be used to add more power to React components with little amounts of effort.
Two of these new features are React.Suspense
and React.lazy()
, which make it very easy to apply code-splitting and lazy-loading to React components.
This article focuses on how these two new features can be used in React applications and the new potentials they open up to React developers.
Writing JavaScript applications has evolved over the last few years. With the advent of ES6(modules), transpilers like Babel, and bundlers like Webpack and Browserify, JavaScript applications can now be written in a completely modular pattern for easy maintainability.
Usually, each module gets imported and merged into a single file called the bundle, and then the bundle is included on a webpage to load the entire app. However, as the app grows, the bundle size starts becoming too large and hence begins to impact page load times.
Bundlers like Webpack and Browserify provide support for code-splitting, which involves splitting the code into different bundles which can be loaded on demand (lazy-loaded) instead of being loaded all at once, thereby improving the performance of the app.
One of the major ways of splitting code is using dynamic imports. Dynamic imports leverage on the import()
syntax, which is not yet part of the JavaScript language standard but is still a proposal that is expected to be accepted soon.
Calling import()
to load a module relies on JavaScript Promises. Hence, it returns a promise that is fulfilled with the loaded module or rejected if the module could not be loaded.
For older browsers, a polyfill like es6-promise should be used to shim
Promise
.
Here is what it looks like to dynamically import a module for an app bundled with Webpack:
When Webpack sees this syntax, it knows to dynamically create a separate bundle file for the moment
library.
For React apps, code-splitting using dynamic import()
happens on the fly if boilerplates like create-react-app
or Next.js
is being used.
However, if a custom Webpack setup is being used, then you need to check the Webpack guide for setting up code-splitting. For Babel transpiling, you also need the babel-plugin-syntax-dynamic-import plugin, to allow Babel parse dynamic import()
correctly.
Several techniques have been in use for code-splitting React components. A common approach is applying dynamic import()
to lazy-load Route components for an application — this is usually referred to as route-based code-splitting.
However, there is a very popular package for code-splitting React components called react-loadable. It provides a higher-order component (HOC) for loading React components with promises, leveraging on the dynamic import()
syntax.
Consider the following React component called MyComponent
:
Here, the OtherComponent
is not required until MyComponent
is getting rendered. However, because we are importing OtherComponent
statically, it gets bundled together with MyComponent
.
We can use react-loadable
to defer loading OtherComponent
until when we are rendering MyComponent
, thereby splitting the code into separate bundles. Here is the OtherComponent
lazy-loaded using react-loadable
.
Here, you see that the component is imported using the dynamic import()
syntax and assigned to the loader
property in the options object.
React-loadable also uses a loading
property to specify a fallback component that will be rendered while waiting for the actual component to load.
You can learn more about what you can accomplish with react-loadable
in this documentation.
In React 16.6, support for component-based code-splitting and lazy-loading has been added via React.lazy()
and React.Suspense
.
React.lazy()
andSuspense
are not yet available for server-side rendering. For server-side code-splitting,React Loadable
should still be used.
React.lazy()
makes it easy to create components that are loaded using dynamic import()
but are rendered like regular components. This will automatically cause the bundle containing the component to be loaded when the component is rendered.
React.lazy()
takes a function as its argument that must return a promise by calling import()
to load the component. The returned Promise resolves to a module with a default export containing the React component.
A component created using
React.lazy()
only gets loaded when it needs to be rendered.
Hence, there is need to display some form of placeholder content while the lazy component is being loaded — possibly a loading indicator. This is exactly what React.Suspense
was created for.
React.Suspense
is a component that is meant for wrapping lazy components. You can wrap multiple lazy components at different hierarchy levels with a single Suspense
component.
The Suspense
component takes a fallback
prop that accepts the React elements you want rendered as placeholder content while all the lazy components get loaded.
An error boundary can be placed anywhere above lazy components to show nice user experience if a lazy component fails to load.
I have created a very simple demo on CodeSandbox to demonstrate using React.lazy()
and Suspense
for lazy-loading components. example on same is available here