loading...
Cover image for Faster React Page Loads With Lazy and Suspense

Faster React Page Loads With Lazy and Suspense

reime005 profile image Marius Reimer ・3 min read

Third-party libraries, images and huge amount of static data can all influence your application bundle size. This can cause unexpected high loading times, which may lead to a bad first site impression. React.Lazy and React.Suspense are common techniques (as of mid 2020), to perform code splitting for bundle size reduction and speeding up page load. In this article I want to show quick you may add code splitting to your React application, highlighting the differences in performance (Lighthouse benchmark/check).

The base application

The idea is that we have a React component, that just displays some static data from a JSON file. I have chosen the programming-quotes-api in order to have some data that makes sense. This data is not being fetched at runtime, but put into a local JSON file, which means it will be bundled into the application. To make the data a bit bigger, I have duplicated its content.

The app boilerplate was created by the common create-react-app tool as described here. From there on, I have created a React component, call it VeryBigJokesList, that displays the static content.

import React from 'react'

import preDefinedJokes from './preDefinedJokes.json'

const VeryBigJokesList = ({ jokes = preDefinedJokes }) => {
  if (!Array.isArray(jokes)) {
    return <p>No jokes found.</p>
  }

  return (
    <ul>
      {
        jokes.map((joke, i) => <li key={i}>{joke && joke.en}</li>)
      }
    </ul>
  );
}

export default VeryBigJokesList;

The non-lazy (eager) case

Usually, I would just import the VeryBigJokesList component and render it in the App component, created by the boilerplate.

import * as React from 'react';

import VeryBigJokesList from './VeryBigJokesList';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <div style={{ maxWidth: 600 }}>
          <VeryBigJokesList />
        </div>
      </header>
    </div>
  );
}

export default App;

This causes to the user load all content from VeryBigJokesList when loading App, since it will be “placed” in the same final bundle. When building the application via yarn build or npm run build, you will see a list of all bundled files of your application.

bundle1

As you can see, the main bundle is yellow highlighted, indicating that its size may be too big. This makes sense, since the JSON data that VeryBigJokesList includes is roughly this size. When running a Lighthouse performance check, you should also see some loading specific issues.

bench1

The lazy case

When planning to use React.Lazy, you mostly need to consider the fact that VeryBigJokesList is being exported via export default and is put as a child (of any depth) of a React.Suspense component. Suspense allows you to render a fallback component (like a loading indicator), while its content is loading.

import * as React from 'react';

const VeryBigJokesList = React.lazy(() => import('./VeryBigJokesList'));

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <div style={{ maxWidth: 600 }}>
          <React.Suspense fallback={<p>Loading list...</p>}>
            <VeryBigJokesList />
          </React.Suspense>
        </div>
      </header>
    </div>
  );
}

export default App;

Adjusting VeryBigJokesList to load lazily was rather simple. If everything worked well, you should see a loading text, before the list is displayed. When building the application, you should also see a difference.

bundle2

The main bundle size has decreased dramatically, since the static JSON content has moved to a different chunk of the bundle. When running a Lighthouse performance check, you should see a difference in loading times.

bench2

Originally published at https://mariusreimer.com on July 26, 2020.

Discussion

markdown guide