Code splitting routers with React Lazy and Suspense

Are you wondering if you should lazy load React components? Does it improve your application performance?

React is fast. But before it becomes fast, your browser has to do a lot of work before it serves your fast React application.

One of the bottlenecks for React is the bundle size.

The problem with a huge bundle file size is that it increase in TTI (time to interactive).

The longer the TTI (time to interactive) is, the more angry users you get.

What is TTI (time to interactive)?

TTI is the result of how long does it take for the user to actually be able to interact with the application or site.

This is measured in time (milliseconds, seconds, minutes, etc).

Let’s take a look at CNN.com and throttle the network to a slow 3G.

CNN slow 3G network test

In each row you can see the JavaScript file being downloaded and executed.

You can also see the compressed size, the uncompressed size, and how long it took to be completed.

If we open on their cnn-footer-lib.min.js file, you’ll see that there is nothing minified about it.

CNN minified footer JS code

And it looks like it contains a lot of the logic for the site in that 1 file.

That’s why it’s taking them more than 10 seconds to download the JS files and execute the code.

React + Webpack = 1 big bundle file

99% of the time when you’re developing in React, you’re going to be using Webpack to help you bundle up everything into a nice package.

Webpack at it’s core, is meant to help hot reload during development, and bundle all your JavaScript files into 1 or multiple JS files.

But if you’re developing React, you’re typically aiming for a single page application, which you’ll typically have 1 JavaScript bundle file.

Webpack bundle output into a single file

Your React files aren’t big, it’s actually some of the smallest. But as you install React core, and other third party libraries that bundle output gets bigger.

And loading a 500kb file size isn’t a pretty user experience.

To give a better user experience, you can do a technique called dynamic importing, also known as lazy loading.

Benefits of Lazy loading React components

The concept of lazy loading our React components is really simple.

Load the minimal code to the browser that will render a page.

Load additional small chunks of code when needed.

By loading less JavaScript code to the browser, that will default to better performance and better TTI results.

The concept of lazy loading may apply to any JavaScript application, but for the sake of simplicity will keep it to React talk.

Code splitting routes with React

In today’s example, I will be starting off from a previous article that explains how to get started with React router.

One thing to note, is that the previous work is using Create React App.

And Create React App has already enabled Webpack to perform code splitting.

The goal now is to utilize the code splitting capabilities, and lazy loading technique, and apply it to the React app.

Another reason I want to use a previous example is because I’m going to demonstrate how to do route base code splitting with React.

I only want to load the JavaScript code that is needed to render a page, at that given time.

And I will be using React lazy and Suspense to load other React files as a user navigates through the application.

Lazy loading with React Suspense and React lazy

Before we jump into implementing the lazy load code, let’s do a quick recap of the current app.

Here are the current pages the cat application has.

Cat application pages

I have 3 pages:

  • A list of cats
  • A form to add a cat name
  • A single view for a cat

Let’s take a quick look at the current code.

React router config file

The file above is a route configuration that just attaches a path to a page.

The next file is the App.js file that grabs the route configuration file and creates routes out of it.

Look at lines 31-44.

Rendering React routes inside a map loop

It goes through a Array.map loop to create a React route component.

Now let’s take a quick look at the React developer tools and see how it looks at initial render.

Inspecting React router output with React developer tools

React renders every page route. Even when you don’t need it at that moment.

Let’s take a quick look at the network tab for JS files.

React output network inspection

The main.[name].chunk.js file is basic Webpack initial code. The big file size is the React cat application.

Our goal is to make our initial load smaller and load in chunks when needed.

Let’s start adding the code!

How to add lazy loading to React router

The first step I took, was to remove route.js file.

The second step was to modify the App.js file. Take a look at the highlighted areas only.

React router with lazy loading code

The highlighted areas shows where the code has changed a bit. Don’t worry I’ll break it down.

Step 1: Import React router Switch component

The first step I took to update the App.js file was in line 5.

I imported the Switch component from react-router-dom.


<Switch>
  // ... routes
</Switch>

The Switch component job is to only render a single route component.

You will never see more than one.

In the React developer tool image above, you might have seen 3 routes. Let’s take a look at the developer tool again to see how many routes will render.

Inspecting react router with lazy loading on React developer tools

And as you navigate through the application, only 1 route component will ever show.

This is helpful because there is no need to have additional code that doesn’t get used at that given time.

Step 2: Create React lazy Components

In line 8 to 10, I created a React lazy components for each page.


// Dynamically import a React component and convert
// it to a React component.
const CatList = React.lazy(() => import('./pages/CatList'));

React lazy let’s you import dynamically a file and covert it into a regular React component.

Step 3: Use React Suspense component

Before I use my React lazy components, I’m going to add the React.Suspense component as a wrapper.

Using React Suspense and React Router Switch component to add a fallback option and reduce amount of code and markup on render method.

React.Suspense is another component provided from the React library.

The React.Suspense component helps as a fallback option, to let your users know it’s loading.

This is due to how dynamic importing works.

So what is dynamic importing?


// Static importing
import React from 'react';

// Dynamic importing
import('./path/to/component');

If we take a look at the code example above, I’ve given 2 different examples of using the keyword import.

Even though it looks like the same, it’s not.

The first import statement can only happen at the top of the file, and only accepts a literal string.

This is good for importing modules that you’ll need in your code file.

The second import example, uses parenthesis, as you would use in a function.

This lets JavaScript know that this will be treated asynchronously, and will return a promise.

Since dynamic importing is asynchronous, that’s where React.Suspense comes into play.

Suspense will display the fallback option until the promise has completed.

The promise in this case, is that a React file has been loaded and executed by the browser.

This will happen as the user goes to each new page.

Step 4: Add our React lazy component to a route


<Route exact path="/" render={() => <CatList cats={cats}/>} />
<Route path="/add" render={props => {
  return <AddCat onSubmit={cat => {
    setCats([...cats, cat])
    props.history.push('/')
  }} />
}} />
<Route exact path="/cat/:name" render={() => <SingleCat cats={cats} />} />

This is a fairly simple step.

Inside my Switch component I’m defining my routes, with a path, and the React.lazy component that I want to use.

And I’m also passing properties down to each React.lazy component, such my list of cats or a onSubmit() handler function.

The result

What I’ve managed to do is grab the entire app and split them into smaller chunks.

Webpack producing smaller chunks illustration

There is always going to be a main bundle JS file. But only 1 small chunk file will be downloaded.

As the user navigates through the app and discovers new pages, other small chunks will be downloaded.

JavaScript chunk files being loaded as discovering new pages

This method makes it easy for the browser to process, and execute quickly.

Smaller chunks of code equals faster TTI results (time to interactive).

Conclusion

Code splitting your React application will bring better performance, because it will only load the minimal code it needs to render a page.

Thus bringing a better user experience, and making your users happy.

Github code: React router with lazy loading

I like to tweet about React and post helpful code snippets. Follow me there if you would like some too!