Aakash Rao N S

@aakashns

Effective Code Splitting in React: A Practical Guide

Large bundle sizes and slow startup is a common problem faced by single-page applications (SPAs), since they typically download all the JavaScript required for every single page of the application right at the start, before rendering a single pixel.

A simple way to solve this problem is to use code-splitting i.e. breaking down the application’s JavaScript into small, modular bundles called chunks, which can be loaded on-demand when a particular feature is accessed. The goal is to keep individual chunks under 100–150 KB, so that the application becomes interactive within 4–5 seconds, even on poor networks.

Source: https://github.com/jamiebuilds/react-loadable

Component-based code splitting

The open source library react-loadable provides a React-friendly API for code splitting, and lets you add breakpoints with just a few lines of code. If you're using create-react-app, Webpack automatically takes care of splitting the bundle and loading chunks on demand under the hood.

Here’s how it works: suppose we want to load and render the component SettingsPage on demand, when the user clicks on a particular or navigates to a particular route. All we need to is wrap it using react-loadable as follows:

Now we can use AsyncSettingsPage just like a normal React component. The module SettingsPage.js and its dependencies are no longer a part of the main JavaScript bundle and are loaded asynchronously when AsyncSettingsPage is rendered for the first time.

While the chunk is loading, the component Loading is rendered in its place. Here’s a sample implementation of Loading:

The prop error is set to a non-null value if the chunk fails to load.

Chunking multiple components together

There are some cases where simple component-based splitting may not be enough. For instance, you may have a set of components that are almost always used together in a several different features. In such a case, it makes sense to have a single chunk which contains the entire set of related components.

Here’s how we might normally export a set of related components:

Assuming the above code is in the file item-list/index.js, we can create another file item-list/async.js with the following contents:

The key change here is in the dynamic import: instead of importing a single component, we are importing all of index.js and extracting the required component in the promise callback.

Chunk naming and optimization

When we build application for production after implementing code splitting, we get many chunks of Javascript that look like this:

File sizes after gzip:
  396.71 KB              build/static/js/main.3a8842c0.js
178.51 KB build/static/css/main.e32b4522.css
68.31 KB build/static/js/6.af93367f.chunk.js
44.34 KB build/static/js/2.6a7f1417.chunk.js
23.61 KB build/static/js/1.bdfdcd83.chunk.js
22.24 KB build/static/js/3.d9e4ee99.chunk.js
19.29 KB build/static/js/4.a66b3cdb.chunk.js
17.1 KB build/static/js/5.f1ce26f7.chunk.js
7.63 KB build/static/js/8.2e807534.chunk.js
6.71 KB build/static/js/9.409015da.chunk.js
5.09 KB build/static/js/7.1b95d8e8.chunk.js
1.71 KB build/static/js/0.6bea2af7.chunk.js
1 KB build/static/js/10.ce9f2434.chunk.js

After looking at this output, we might want to remove some of the last few chunks since they’re really small. But we don’t know which split is causing which chunk to be created. This is where chunk naming can be helpful.

We can use a magic comment inside the import that tells Webpack to use the given name for a specific chunk:

Once all the chunks are named, we can identify the splits that lead to smaller chunks:

File sizes after gzip:
  312.09 KB  build/static/js/main.491eaaf4.js
181 KB build/static/css/main.ac06cedb.css
68.88 KB build/static/js/settings.1525d075.chunk.js
45.08 KB build/static/js/alerts.0f5ad4d6.chunk.js
23.62 KB build/static/js/profile.199c7f90.chunk.js
22.24 KB build/static/js/history.07ccea31.chunk.js
19.3 KB build/static/js/actions.903378a5.chunk.js
8.87 KB build/static/js/events.f540de3a.chunk.js
7.62 KB build/static/js/colors.89aa1e6f.chunk.js
6.7 KB build/static/js/posts.929f04fc.chunk.js
5.1 KB build/static/js/post-details.6c133f77.chunk.js
1.71 KB build/static/js/friend-list.be516e45.chunk.js
1.01 KB build/static/js/edit-avatar.33a4ff21.chunk.js

At this point, we can choose to remove or combine some of the smaller chunks (< 20–30 KB in size), since the overhead of loading a 5 KB chunk might be higher than combining it with one of the larger chunks. Play around with different splits and see what works best for you.

Analyzing the Bundle Size

Source map explorer analyzes JavaScript bundles using the source maps. This helps you understand where code bloat is coming from. To add Source map explorer to a Create React App project, run the following command:

npm install --save source-map-explorer

Then in package.json, add the following line to scripts:

"scripts": {
"analyze": "source-map-explorer build/static/js/main.*",

Then to analyze the bundle run the production build then run the analyze script.

npm run build
npm run analyze
Source map explorer

Look for the largest contributors to the bundle size as possible candidates for code-splitting. Also consider removing or pruning large dependencies from node_modules.

Summary

Here are steps for achieving effective code splitting in React applications:

  1. Use react-loadable to achieve component-based code splitting and load Javascript bundles for different parts of the application on demand.
  2. Chunk multiple components that are frequently used together into a single file using the import("./index").then trick.
  3. Name your chunk using the magic comment /* webpackChunkName: xxx */ and optimize bundle sizes so that they are neither too small nor too large.
  4. Use source-map-explorer to identify possible candidates for code splitting.

I’ve skipped over many details to keep this article short and focus on the practical aspects of code splitting. Following are some good places to learn more about the topic:

More by Aakash Rao N S

Topics of interest

More Related Stories