Code splitting with lazy and Suspense in React

Knowing how to split your code with Suspense and lazy was discussed in a previous post. In short, you need to dynamically import your component using React.lazy and then render this variable as a child of a Suspense component.

In this post let’s look at what is happening behind the scenes for the end user when using Suspense and lazy.

Given this component that renders a lazy-loaded component:

function App() {
  const MyLazyComponent = lazy(() => import('./components/MyLazyComponent'));

  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <MyLazyComponent />
      </Suspense>
    </div>
  );  
}

We can see some interesting network traffic, and by interesting, I mean the src_components_MyLazyComponent_js.chunk.js file

Lazy loading a React component using React.lazy and Suspense

If you have a look at the response in the chunk file you’ll see similar Javascript to what you see in the bundle.js file from webpack. Inside of the webpack code, you’ll find your component definition in plain Javascript. For example, MyLazyComponent looks like this:

export default function MyLazyComponent() {
	return (
		<div>
			<h1>Lazy</h1>
		</div>
	)
}

Now looking into the chunk file you can see the MyLazyComponent function.

function MyLazyComponent() {
  return /*#__PURE__*/(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxDEV)("div", {
    children: /*#__PURE__*/(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxDEV)("h1", {
      children: "Lazy"
    }, void 0, false, {
      fileName: _jsxFileName,
      lineNumber: 4,
      columnNumber: 4
    }, this)
  }, void 0, false, {
    fileName: _jsxFileName,
    lineNumber: 3,
    columnNumber: 3
  }, this);
}

If you look back into the bundle file you’ll see that MyLazyComponent isn’t defined.

What happens if we only load our lazy component after a button click? Let’s update the app and see. Our App component now looks like this:

function App() {
  const [buttonClicked, setButtonClicked] = useState(false);
  const MyLazyComponent = lazy(() => import('./components/MyLazyComponent'));

  const setButtonClickedToTrue = () => {
    setButtonClicked(true);
  }

  return (
    <div>
      <button onClick={setButtonClickedToTrue}>Click me...</button>
      {buttonClicked && <
        Suspense fallback={<div>Loading...</div>}>
        <MyLazyComponent />
      </Suspense>
      }
    </div>
  );  
}

So the things to notice in the updated App component are that we are now using state through the useState hook and we are inline rendering our lazy component based on the status of buttonClicked.

If we look at the network tab upon the initial load of our app we see this:

Initial load of a React component with a lazy loaded component not yet loaded

And our app looks like this:

Initial status of the app before lazy loading the component

Once we click the button we can see our chunk being loaded in the network tab and our component rendered in the application:

Our app with the lazy component loaded

That is a basic look at exactly how the chunked files are delivered to the end user using Suspense and lazy.

Code splitting with lazy and Suspense in React

Leave a Reply

Your email address will not be published.

Scroll to top