Code Splitting Make a Huge Different
Adam C. |

React is usually used to build a single page application, which means you only load the application code (HTML, CSS, Javascript) once. It works perfectly for a small application, but for a complex application, to load all Javascript code into a bundle (i.e., main.js) could cause the initial page loading to become very slow. So when you find that your app has a performance issue, the first thing you should try is code-splitting.

Photo by Andrew Ridley on Unsplash

What's Code Splitting

According to ReactJS doc:

Code-Splitting is a feature supported by bundlers like Webpack, Rollup and Browserify (via factor-bundle) which can create multiple bundles that can be dynamically loaded at runtime.

Basically, instead of loading a giant bundle, we split the code into smaller pieces called chunks. So then, they are only loaded when needed.

If you need code spitting, then likely you are using a react router. With that, we could have a unique URL for each page (or component,) and make our Single Page Application work like many pages, although there could be just one bundle javascript file holding the data and actions of all pages. 

How to Code Splitting with React Router

By using React.lazy and Suspense, code splitting with React Router could not be easier. Here is how we optimize for one of our clients. 

The original App.js looked like this:

import React from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';

import Header from './Header';
import './App.css';

import UserTrail from './UserTrial/UserTrail';
import Output from './UserTrial/Output';
import UserUpload from './UserTrial/UserUpload';
import Demo3 from './UserTrial/Demo3';
import Sentiment from './Sentiment';
import FraudTracker from './FraudTracker';
import Diff from './Common/Diff';
import NegativeTermsViewer from './Common/NegativeTermsViewer';
import Dashboard from './Dashboard';

class App extends React.Component {
  render() {
    return (
      <div>
        <Header />
        <Switch>
          <Route exact path="/" component={Dashboard} />
          <Route exact path="/dashboard" component={Dashboard} />
          <Route path="/user-trial" component={UserTrail} />
          <Route path="/output" component={Output} />
          <Route path="/user-upload" component={UserUpload} />
          <Route path="/demo" component={Demo3} />
          <Route path="/sentimentor" component={Sentiment} />
          <Route path="/fraud-tracker" component={FraudTracker} />
          <Route path="/diff" component={Diff} />
          <Route
            path="/negative-terms-viewer"
            component={NegativeTermsViewer}
          />
        </Switch>
      </div>
    );
  }
}
export default App;

And we changed it to:

import React, { Suspense, lazy } from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';
import Header from './Header';
import './App.css';

const UserTrail = lazy(() => import('./UserTrial/UserTrail'));
const Output = lazy(() => import('./UserTrial/Output'));
const UserUpload = lazy(() => import('./UserTrial/UserTrail'));
const Demo3 = lazy(() => import('./UserTrial/Demo3'));
const Sentiment = lazy(() => import('./Sentiment'));
const FraudTracker = lazy(() => import('./FraudTracker'));
const Diff = lazy(() => import('./Common/Diff'));
const NegativeTermsViewer = lazy(() => import('./Common/NegativeTermsViewer'));
const Dashboard = lazy(() => import('./Dashboard'));

class App extends React.Component {
  render() {
    return (
      <div>
        <Header />
        <Suspense fallback={<div>Loading...</div>}>
          <Switch>
            <Route exact path="/" component={Dashboard} />
            <Route exact path="/dashboard" component={Dashboard} />
            <Route path="/user-trial" component={UserTrail} />
            <Route path="/output" component={Output} />
            <Route path="/user-upload" component={UserUpload} />
            <Route path="/demo" component={Demo3} />
            <Route path="/sentimentor" component={Sentiment} />
            <Route path="/fraud-tracker" component={FraudTracker} />
            <Route path="/diff" component={Diff} />
            <Route
              path="/negative-terms-viewer"
              component={NegativeTermsViewer}
            />
          </Switch>
        </Suspense>
      </div>
    );
  }
}

export default App;

Believe it or not, only a few lines changed made a huge difference to the page load time, which dropped from 19.45 seconds to 1.76 seconds.

Without Code Splitting
With Code Splitting

Note that React.Lazy and Suspense only works for client-side rendering. If you are using Server-side rendering, we recommend using Loadable Components, which is actually what Next.JS uses.

Component-Based Code Splitting

Update (12/12/2020) In case the router-based code splitting is not enough. For example, you have ten page-routes, some pages have much more importing than others, then those pages will be still slow. To fix this, we could do component-based code splitting.

Dynamic Import() in ComponentDidMount

If you load  a lot of JSON data from local, instead of doing this:

import SP500Data from "../../data/Reporting-risk-analysis-SP500";
import SectorData from "../../data/Reporting-risk-analysis-sectors";
import MarketCapData from "../../data/Reporting-risk-analysis-market-cap";
import StateData from "../../data/Reporting-risk-analysis-states";

You can move them into ComponentDidMount life cycle, and then set them into the local states like below:

async componentDidMount() {
  const SP500Data = await import(
    '../../data/Reporting-risk-analysis-SP500'
  ).then((data) => data);
  const SectorData = await import(
    '../../data/Reporting-risk-analysis-sectors'
  ).then((data) => data);
  const MarketCapData = await import(
    '../../data/Reporting-risk-analysis-market-cap'
  ).then((data) => data);
  const StateData = await import(
    '../../data/Reporting-risk-analysis-states'
  ).then((data) => data);

  this.setState({
    loading: false,
    SP500Data,
    SectorData,
    MarketCapData,
    StateData,
  });
}

Dynamic Import() in Action

If you use import() data in your Action function, you can use Dynamic Import() in your Async function like below:

 handleChange = async () => {
 	 const currentClassActions = await import(
     '../../data/classActions'
   ).then((classActions) => _.filter(classActions, ['Ticker', value]));
 }

Note that Dynamic import() is an async function, so you should use await to get the data.