Code Splitting with React and React Router

Code splitting has gained popularity recently for its ability to allow you to split your app into separate bundles your users can progressively load. In this post we’ll take a look at not only what code splitting is and how to do it, but also how to implement it with React Router.

Note that this article just one part of my comprehensive new React Router course.

Also, I’ve created a video to go with this article:

It’s 2018. Your users shouldn’t have to download your entire app when all they need is a piece of it. If a user is creating a new post, it doesn’t make sense to have them download all the code for the Registration view. If a user is registering, they don’t need the huge rich text editor your app needs on the Settings view. It’s wasteful and some would argue disrespectful to those users who don’t have the privilege of unlimited bandwidth. This idea has not only gained much more popularity in recent years, but it’s also become exponentially easier to pull off — it even has a fancy cool name — code splitting.

The idea is simple, don’t download code until the user needs it. In practice, it can be a little more complicated. The reason for this isn’t because code splitting itself is terribly difficult, but that there are various tools to do it and everyone has an opinion on which is the best. When you’re first starting out, it can be hard to parse what is what.

The two most common approaches are using Webpack and its bundle loaderor the ECMAScript dynamic import() proposal which is currently stage 3. Any chance I get to not use webpack, I take, so we’ll be using dynamic import() in this post.

If you’re familiar with ES modules, you know that they’re completely static. What that means is that you must specific what you’re importing and exporting at compile time, not run time. This also means that you can’t dynamically import a module based on some condition. imports need to be declared at the top of your file or they’ll throw an error.

if (!user) {
  import * as api from './api' // 🙅‍🚫. "import' and 'export' may only appear at the top level"

Now, what if import didn’t have to be static? Meaning what if the code above worked? What benefits would that give us? First it would mean we could load certain modules on demand. That would be pretty powerful since it would enable us to get closer to the vision of only downloading code the user needs.

if (editPost === true) {
  import * as edit from './editpost'

Assuming editpost contained a pretty large rich text editor, we’d make sure we didn’t download it until the user was actually ready to use it.

Another cool use case of this would be for legacy support. You could hold off on downloading certain code until you were certain the user’s browser didn’t already have it natively.

Here’s the good news (that I kind of already alluded to earlier). This type of functionality does exist, it’s supported by Create React App, and it’s currently in Stage 3 of the ECMAScript process. The difference is that instead of using import as you typically would, you use it like a function that returns you a promise that resolves with the module once the module is completely loaded.

if (editPost === true) {
  import * as edit from './editpost'

Pretty rad, right?

Now that we know how to dynamically import modules, the next step is figuring out how to use it with React and React Router.

The first (and probably biggest) question we need to ask ourselves when it comes to code splitting with React is where should we split at? Typically, there are two answers.

  1. Split at the route level. 🙂
  2. Split at the component level. 😃

The more common approach is to split at the route level. You already split your app into different routes, so adding in code splitting on top of that feels pretty natural. How would this actually look?

Let’s start off with a basic React Router example. We’ll have three routes, //topics/settings.

import React, { Component } from 'react'
import {
  BrowserRouter as Router,
} from 'react-router-dom'
import Home from './Home'
import Topics from './Topics'
import Settings from './Settings'
class App extends Component {
  render() {
    return (
<li><Link to='/'>Home</Link></li>
<li><Link to='/topics'>Topics</Link></li>
<li><Link to='/settings'>Settings</Link></li>
<hr />

          <Route exact path='/' component={Home} />
          <Route path='/topics' component={Topics} />
          <Route path='/settings' component={Settings} />
export default App

Now, say our /settings route was super heavy. It contains a rich text editor, an original copy of Super Mario Brothers, and an HD image of Guy Fieri. We don’t want the user to have to download all of that when they’re not on the /settings route. Let’s use our knowledge of dynamic imports and React to code split the /settings route.

Just like we solve any problem in React, let’s make a component. We’ll call it DynamicImport. The goal of DynamicImport is to dynamically load a module, then, once it’s loaded, to pass that module to its children.

const Settings = (props) => (
  <DynamicImport load={() => import('./Settings')}>
    {(Component) => Component === null
      ? <Loading />
      : <Component {...props} />}

The above code tells us two important details about DynamicImport. First, it will receive a load prop which when invoked, will dynamically import a module using the dynamic import syntax we covered earlier. Second, it will receive a function as its children which will need to be invoked with the imported module.

By Tyler McGinnis

This article originally appeared in