Code-Split Your React App with Lazy + Suspense

avatar
Marshal Murphy
August 17, 2020 - 6 MIN READ

When you deploy a React application using a tool like Webpack, you are employing a technique called bundling.

Build tools like Webpack are, after all, module bundlers.

So when you arrive at a page served by a web application, the page requests a bundle; that is, a file or files that contain all of the code necessary for that page to function.

Often when we build a page, however, we include quite a bit of code that isn't immediately necessary.

To keep our application lean and fast, we should strive to serve only what is absolutely necessary for the UI on first paint.

One way to achieve this is by code splitting.

Feature

Code splitting allows us to defer to the loading of various resources until later, when they are actually needed.

For example, let's say a user hits a route and arrives at a page in your web application.

Maybe this page has a number of heavy modals.

Maybe this page has a number of routes that load additional content elsewhere.

Maybe it contains a heavy library that is needed later, but is being loaded first on the home page.

If our goal is to render the page contents as fast as possible, we need to abstract away everything that isn't immediately required and acting to slow your load time down.

Let's explore a few ways to code-split your web app using React's lazy method and Suspense component.


Setup

First, let's bootstrap a project with create-react-app.

In your command line:

> npx create-react-app codesplitting-in-react

Then launch the dev server at port:3000 by following up the installation with:

> cd codesplitting-in-react
> yarn start

Excellent, we have the basic spinning React logo that we've come accustomed to.

Open the project in your favourite editor and let's get started.


Code Splitting Project Setup

In React, we have the ability to import a function called lazy, and a component called Suspense.

What lazy does is allow us to differ the loading of a component or resource until it is needed.

When compined with Suspense, the lazy component will only be rendered once loaded, and we are able to provide a fallback while it loads (such as loading...).

Let's work through an example.

Reach Router and Route Setup

Shut down the server with control + c and install Reach Router:

Reach Router is a simple, lightweight router developed by the people behind React Router.

In your terminal:

> yarn add @reach/router
> yarn start

What we are going to do now is:

  1. pull the code out for the spinning React logo into it's own component called Main.js.
  2. use ReachRouter to show Main by default.
  3. create a new component that is routed to when we click the React logo.

So, go ahead and create a new file called Main.js and pull out the code from within the <header> from App.js.

Our App.js will now look a whole lot skinnier.

Make sure you import Main.js and place it where you removed it, as seen below.

As always, avoid those class methods.

Great!

So visually, nothing will have changed.

While we pulled the code into a new component, the code didn't change, and so everything will still look the same.

Now let's import ReachRouter and use our Main component as our default route.

ReachRouter is very straight forward to use:

  1. Import Router from the ReachRouter library.
  2. Wrap your routes in <router>.
  3. Provide a path prop for each route, with Main being the default.

Update App.js now as follows

Again, nothing will have changed visually, but now we have Main set as our default route.

Now let's create a second child route, assign it a path, and have the React logo link to this path.

Create a new file called SplitThis.js, and set it up to simply render it's name as an <h1>.

Next, import this component into App.js and add it as a child route under <Main> with the path "/split".

Then, in Main, import Link from @reach/router to wrap the React logo in, and direct it to "/split".

Now we can see that our routes are set up, and that clicking the logo takes us to our other page.

Image


Code-Splitting Routes

First, to get an understanding about how bundling works, let's open up our inspector in our browser.

In the inspector, select Network -> JS, and refresh the page.

Image

When you refresh, you will see the different bundles coming through the network.

You can also see their sizes. Right now, all of our bundles are very, very small.

Image

Typically we wouldn't even consider codesplitting unless a bundle was greater than 30kb.

But for the sake of example, let's import lazy and Suspense and split out our SplitThis route.

Update App.js to include the above imports, wrap the routes in Suspense with a fallback tag, and import SplitThis lazily.

With SplitThis lazy loaded within Suspense, that component will not be included in the main bundle.

Instead it will be retrieved from the server only when the user requests it by clicking on the React logo.

Watch what happens now when we load the app and then navigate to our SplitThis route:

Image

We can see that the SplitThis route was loaded as a seperate bundle.

Another very important thing to note is that once that bundle is loaded and you navigate elsewhere, it won't be reloaded when you return.

The bundler is smart enough to see that the bundle it requires was already loaded previously and so does not attempt to retrieve it again.


Code-Splitting Libraries

Some libraries are very heavy.

Typically, if you do not plan to use absolutely all of a library, you should only be importing the pieces that you need.

In the same way as we did with code splitting a route, let's do the same with a library within a component.

First, let's install a heavy library to import, such as moment.js.

Shut down your server and install the moment library.

> yarn add moment
> yarn start

Now simply import moment into SplitThis.js.

Additionally, let's allo import lodash, which comes included with create-react-app.

Now take a look at the load on the Network.

Upon clicking our child route, we see network results like this:

Image

The first 3 lines are our initial load into main, and then when we click our child route we recieve 2 more bundles, one of them being fairly large since it is importing 2 large libraries.


Learnings

By using React’s lazy method and Suspense component we gain the ability to control how the application bundler decides what goes where.

The result: lean pages that load quickly (if used correctly).

No one wants to work super hard to create an awesome app, only to have users dropping off because of a slow first paint.

So, be lazy.

Marshal Murphy 2020