Vehid Trtak

Vehid Trtak

en-US

Code splitting in React

. . .
image

What is code splitting ?

Code splitting is separating our bundled code into smaller chunks of code so we can download and use only the parts of code we need. In React and Javascript frameworks in general, all the code is bundled in one large file. And this makes it easier to add it to the HTML using only one link. This also reduces servers call, and in theory, it should speed up the page loads and lower traffic for that page. But after we keep working on the app, we keep on adding files to our project. And adding more to our project makes that one Javascript file larger and larger, and that gets it slower and slower. Initially, one file was great, but after we keep adding we get it slower, so now we need to find a way to improve our page speed and that is where Code splitting comes into play.

Why do we need code splitting ?

We use Code splitting to separate code and dependencies in multiple files so we can only use code that we need at that time. When you think about it we don't need to use all components or files at the same time, we have multiple pages, we have multiple components and we download them all when we open our application, even if the user doesn't visit admin pages, or maybe report pages we still download them with that one Javascript file. So when the application gets to a point of slowing down, we should start considering code splitting.

We can dynamically import components that we don't need at a certain time, so when we open the home page, we only download components and code that we need for the home page, which means that we don't have to download everything just to show few of them.

NOTE: Multiple studies have shown that pages with lower response times have better impression on the user, and they are more likely to come back again Studies link

How can we start code splitting ?

If we use create-react-app, Next.js or Gatsby, or similar tools, we have a code-splitting setup already using Webpack. If you are using some custom tool, or you created your own, you can add code splitting yourself and add it to your project. Here is a link of setting up code splitting and lazy loading in your app using Webpack. But it can be set up with other bundlers like Rollup, Browersify or Parcel or any other bundler.

We have few ways we can add code splitting to our React application:

Dynamic import()

Dynamic import has been added to the Javascript in ES6, and we can natively start code splitting our Javascript code. With dynamic imports it's quite easy:

Before:

import { Header } from './Header';

<Header />;

After:

import React, { useState } from 'react';

export default function App() {
  const [Header, setHeader] = useState(null);

  React.useEffect(() => {
    import('./Header')
      .then((component) => {
        setHeader({ Component: component.default });
      })
      .catch((err) => {
        // Handle failure
      });
  }, []);

  return <div className='App'>{Header ? <Header.Component /> : null}</div>;
}

Or with async / await:

import React, { useState } from 'react';

export default function App() {
  const [Header, setHeader] = useState(null);

  React.useEffect(() => {
    (async () => {
      const component = await import('./Header');
      setHeader({ Component: component.default });
    })();
  }, []);

  return <div className='App'>{Header ? <Header.Component /> : null}</div>;
}

Or if you want to use it with Named Exports instead of Default Export as in the example above, you can do it like this:

import React, { useState } from 'react';

export default function App() {
  const [Header, setHeader] = useState(null);

  React.useEffect(() => {
    import('./Header')
      .then(({ Header }) => {
        setHeader({ Component: Header });
      })
      .catch((err) => {
        // Handle failure
      });
  }, []);

  return <div className='App'>{Header ? <Header.Component /> : null}</div>;
}

Or with async / await:

import React, { useState } from 'react';

export default function App() {
  const [Header, setHeader] = useState(null);

  React.useEffect(() => {
    (async () => {
      const { Header } = await import('./Header');
      console.log(Header);
      setHeader({ Component: Header });
    })();
  }, []);

  return <div className='App'>{Header ? <Header.Component /> : null}</div>;
}

As you can see it's easy, but the syntax is a bit weird. We have a second way of code splitting in React.

React.lazy() and React.Suspense

React v16.6.0 introduced a way to do code-splitting in React, they added two features React.lazy() and React.Suspense that enables us to use code splitting directly in React.

  • React.lazy()

Note: At the time this blog is written, React.lazy() and Suspense are not available for server-side rendering, but they should be in the future. For now, we can use Loadable Components to use code-splitting for server-side rendering.

The React.lazy enables us to dynamically import components, for example:

Before:

import React from 'react';
import Header from './Header';

export default function App() {
  return (
    <div className='App'>
      <Header />
    </div>
  );
}

After:

import React, { lazy, Suspense } from 'react';
const Header = lazy(() => import('./Header'));

export default function App() {
  return (
    <div className='App'>
      <Suspense fallback={<div>Loading stuff...</div>}>
        <Header />
      </Suspense>
    </div>
  );
}

Here you can see how much easier this is than the Dynamic import, we just import lazy and Suspense from React and use them to lazy import components then use Suspense to display some fallback value while we wait for our component. Don't forget to wrap the lazy imported component in Suspense, since React expects to have a fallback while we wait. We can also use single Suspense to wrap multiple lazily imported components:

import React, { lazy, Suspense } from 'react';
const Header = lazy(() => import('./Header'));
const Footer = lazy(() => import('./Footer'));

export default function App() {
  return (
    <div className='App'>
      <Suspense fallback={<div>Loading stuff...</div>}>
        <Header />
        <Footer />
      </Suspense>
    </div>
  );
}

NOTE: ⚠️ At the time this blog was written, React.lazy only supports Default Exports, and if you want to use Named Exports you can create an intermediate module that can reexport it as default. This ensures that tree shaking keeps working and that you don’t pull in unused components.

import React from 'react';

export const Header = () => {
  return <div>Header</div>;
};
export { Header as default } from './Header';
import React, { lazy, Suspense } from 'react';
const Header = lazy(() => import('./ExportDefault'));
const Footer = lazy(() => import('./Footer'));

export default function App() {
  return (
    <div className='App'>
      <Suspense fallback={<div>Loading stuff...</div>}>
        <Header />
        <Footer />
      </Suspense>
    </div>
  );
}

This gets a bit annoying that you need to create an additional component just to export it, in my opinion, it's better to use default export for components that are lazily loaded.

  • Error boundaries

When using lazy() and Suspense there is a possibility that the module fails to load due to some network issues or something else and it will throw an error. We have to handle those errors so our user experience doesn't suffer. To prevent that we use ErrorBoundaries that wraps our Suspense and lazy, and handles errors that might happen while loading components lazily. If you want to create your ErrorBounary you can visit React official documentation and use examples there, but at this time there is no equivalent of ErrorBoundary written using Hooks, so it will have to be written with class components. I will use a library called "react-error-bounndary", since I don't like working with classes πŸ˜….

import React, { lazy, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
const Header = lazy(() => import('./ExportDefault'));
const Footer = lazy(() => import('./Footer'));

export default function App() {
  function ErrorFallback({ error, resetErrorBoundary }) {
    return (
      <div role='alert'>
        <p>Something went wrong:</p>
        <pre>{error.message}</pre>
        <button onClick={resetErrorBoundary}>Try again</button>
      </div>
    );
  }

  return (
    <div className='App'>
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        <Suspense fallback={<div>Loading stuff...</div>}>
          <Header />
          <Footer />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

Much easier than creating your own using classes, but it will change when they add equivalent to Hooks.

Where to use code splitting πŸ€”

One of the ways that you should probably use code splitting is on your routes. If you think about it, many users don't visit all of our routes when they visit our application, so why slow down the app and increase waiting time just to download code that they probably won't need. We can determine using some statistic tools what pages are most used and users are most likely to visit then they enter our app, and based on that create code splitting for them.

import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const Reports = lazy(() => import('./Reports'));

export default function App() {
  return (
    <div className='App'>
      <Router>
        <Suspense fallback={<div>Loading stuff...</div>}>
          <Switch>
            <Route exact path='/' component={Home} />
            <Route path='/reports' component={Reports} />
          </Switch>
        </Suspense>
      </Router>
    </div>
  );
}

This is how Javascript code looks before we use code splitting:

main.chunk.js
main.12381831283.hot-update.js
bundle.js
0.chunk.js

And this is how it looks after we add code splitting:

main.chunk.js
main.12381831283.hot-update.js
bundle.js
0.chunk.js
2.chunk.js

And you can see here that we have an additional file 2.chunk.js, that was created when we opened for example /home route, and we got only Home.js and not the other components.

If you want to check how files look in your app before and after you do code splitting, you can open Inspect in Chrome and then go to the Network tab, and select JS in filters so you get the only list of Javascript files. One more useful thing in chrome is the Coverage tab that helps us see all Javascript files that are downloaded and what code percentage of those files are used. You can open Inspect in Chrome and press Ctrl+Shift+P and type Show Coverage and press enter, and it should show you the coverage tab. Here is a link of how to use that Coverage tab to see unused code, I will not go into it since this post is about code splitting.

One more case where we could use code splitting is in the application that has admin and regular users. For example we login in to our application as a normal user, and when we log in we get all the code for our app, including the admin panel that we don't have access to. So we download all that code and slow downloading and we want to use it at any point since we don't have access. This is a great way to introduce code splitting to download only part that we can use and access, and improve the speed of our app and also don't allow curios people to sniff and look around our admin panel code 🧐

If you want to test the speed of your application there is an amazing tool in Chrome called Lighthouse, you can access it by opening Inspect in Chrome and then going to the Lighthouse tab. There is an option to test many things for your app on Desktop and Mobile.

NOTE: when testing speeds of your app it's important to know that a development app is much slower than a production app since many bundling features are not used in development app to improve debugging and speed up development since we don't have to do additional stuff like minification, uglification, etc.

Conclusion

To Recap, we went through what is code-splitting, how to use it, and example where we might use it. This should be a good start for you to start adding code splitting to your application and see those loading times improving. If you find this post informational and useful, please share it with others so they can benefit from it. Also, don't stop at this post, read more about it, follow the links I provided in the post and also visit the official documentation of React for more information.