Code splitting is generally regarded as a good thing. It’s why the import syntax has been pushed so heavily in the last few years. It’s also why Webpack and Next.js display it prominently on their home pages as a key feature. It’s compelling for good reason, why load all of the data in the webpage when the user might not need it? We want the perceived loading for our site to be consistently low and for users to only download the code that they need.

One way in which we can achieve this is through using dynamic imports. Unlike static imports, dynamic imports return a promise that gets resolved when necessary, allowing us to delay the loading of a module. A user’s journey may avoid using the aforementioned module entirely. Visually, dynamic imports vary from the more commonly seen static imports. The code snippet below highlights this:

Line 1 shows the syntax for static import, while line 3 shows the syntax for a dynamic import. It’s a little more verbose, and that’s because a dynamic import returns a promise that resolves to a module. Let’s see dynamic imports in action.

Say your web app is an Event Planner. You’ve got your CMS that contains all of the event details in a nice JSON object. You:

Request the data

Display a loading component

and either:

Receive the data

Render the event details

or:

Receive an error

Render an error component

Before using React.lazy, your code might look a little like this:

There’s nothing crazy going on here. We’re importing 3 components that get rendered under certain conditions. When our component mounts we make a request for our CMS data. Only when it returns successfully do we dynamically import our eventCards component. We save the module to the state and load it within our render method. The code on line 37 isn’t the clean React code we strive to achieve. We’re not using the declarative JSX syntax we know and love so much. Now imagine doing this with several components, it would make any sane JavaScript developer wince. We deserve better than that.

With React.lazy(), we can automatically load a given component’s code bundle only once we go to render the component:

Everything feels a little cleaner this time around. We’re returning normal looking JSX, and the React.lazy() on line 12 makes our import logic a lot more declarative.

We had to make few notable changes to our code to lazy load our modules. The most significant is the use of the new Suspense component that we’ve imported from React. The Suspense component needs to be placed anywhere above our lazily-loaded module. If the module hasn’t fully loaded the fallback component is rendered in its stead. If you move your eyes up to line 27 you’ll see that I’ve taken the liberty of wrapping our Suspense component in an ErrorBoundary . If our EventCards component fails or throws an error we let it propagate upwards and our ErrorBoundary handles it. You can read more about ErrorBoundaries here, I didn’t go into anymore detail as it’s out of the article’s scope.

I’ve also gotten the Loading component to render in two separate places. It’s included on line 24 as we wait for the data to come back from our request to the CMS. It is again included on line 28 as React goes to load the actual module.