Code splitting with Create React App

Vikram Thyagarajan
Founding Ithaka
Published in
4 min readApr 6, 2018

One of the main benefits of React, Componentization and Webpack is that content can be loaded on demand when required. However it can be hard to figure out when and how to split code into different chunks for loading.

In this tutorial we’ll look at one of the easiest ways to do it - by the pages in the app. Let’s take an example of an app with Redux to manage app state and React Router for page navigation and routing. Now in this case, before we load any components we need to initialize app state and the Redux Store. The reason being that we cannot render anything until we know what to render. And this is mostly defined by the app state stored in redux.

So we’d like to break down this redux logic into its own chunk and load it at the start. Then, based on the route the page is on, we can download the different components that are to be shown and render them. For this we’d like each component that is being rendered in the route to be in its own chunk

Breaking redux into its own chunk

This is actually very simple. By default CRA puts all code into a single main.js chunk. So this already contains our redux logic. The only issue is that it contains ALL our code. We’ll look into breaking the components into separate chunks in the next section.

For now one important thing to touch upon is that your index.js (entry file which renders the ReactDOM) should contain very few imports and dependencies. Make sure you only include and import the lodash and rxjs (for example) operators required. This goes a long way in bringing down our app size.

We will also look into loading our reducers asynchronously in the next tutorial. This is so only the most important reducers are loaded in the start, and we further cut down our size of main.js

Breaking each page into its own chunk

Webpack provides code splitting in a couple of different ways. Here we will at Dynamic Imports, which is an approach that ties well with React Router.

What we require for our chunks is so all dependencies in are included in the chunk for the component. The drawbacks of this approach is that dependencies could be loaded multiple times. So loads might be imported in our page, as well as main.js but the code for each import would be present in both bundles.

However at the moment, since we’re importing each lodash operator as required, we can ignore it for the time being. Dynamic importing is done in Webpack by

import(/* webpackChunkName: “App” */ ‘./App.js’))
.then(() => {console.log(‘imported’);}
.catch((err) => {console.error(‘error during import’, err);}

Instead of our usual import App from './App.js'. This tells webpack to break all code referenced in app.js to be bundled into its own file. And this code will only be fetched when it is called.

NOTE: The dynamic import syntax returns a promise instead of synchronously returning the component as it needs to fetch and load the component from the new file

Tying dynamic imports with React Router

Now you must wondering how this works in the context of React Router. The dynamic import returns a promise whereas our usual code for loading a component to a route is by passing the component (loaded synchronously) to the Route.Component prop.

Well, React Router does not provide direct support to pass a component via promises, so we’d have to do a few workarounds. Let’s break it down in steps to better explain it-

Creating a wrapper component

The Route component requires a component to render at all times, so we will be providing a wrapped component. This component will render a dummy DOM (a loading text) until the promise to fetch the new page resolves

const asyncComponent = getComponent => {
class AsyncComponent extends React.Component {
static Component = null;
state = { Component: AsyncComponent.Component };
componentWillMount() {
if (!this.state.Component) {
getComponent().then(({ default: Component }) => {
AsyncComponent.Component = Component;
this.setState({ Component });
});
}
}
render() {
const { Component } = this.state;
if (Component) {
return <Component {...this.props} />;
}
return <div>Loading…</div>;
}
};
return <AsyncComponent />
};

Connecting the Wrapper component to the Router

The Route component accepts a render function which can return a component that is to be shown. We will use this to render the wrapper component.

ReactDOM.render(
<Provider store={Store}>
<BrowserRouter>
<div>
<Route path="/" render={(props) => asyncComponent(() => {
return import(/* webpackChunkName: "App" */ './App.js')
})}>
</div>
</BrowserRouter
</Provider>,
document.getElementById('root')

Here the asyncComponent function will return the wrapper component which will be shown. This wrapped component shows a loading div until the dynamic import is resolved.

Building and testing it out

Our app is now split successfully and once we run our build process, it should show the 2 different built files-

The files created after running npm or yarn build

Also on viewing of the network tab, it shows the loading of main.js and app.js at different times. As our app has more pages, the functioning and loading times of this page would remain unaffected.

Using a custom solution

React Loadable is an out of the box solution for code splitting that does everything that is explained in the post and some additional features like Server Side Rendering. A small snippet for the same can be found in the official docs

--

--

Vikram Thyagarajan
Founding Ithaka

Software Developer with clients like PepsiCo, Unilever and McKinsey & Co. Passionate about Technology, Travel and Music. Speaks of self in 3rd person.