Code Splitting in React (An Overview)

Code Splitting in React (An Overview)

Lighthouse displays a failed audit when a significant time is taken to execute all the JavaScript on your app, so how do you optimize this and more?

ยท

4 min read

Have you seen a message like this when you generate lighthouse report for an application?

p0Ahh3pzXog3jPdDp6La.png

This is caused by sending large JavaScript payloads which in turn impacts the speed of your site significantly. Instead of shipping all the JavaScript to your user as soon as the first page of your application is loaded, split your bundle into multiple pieces and only send what's necessary at the very beginning.

What is Code Splitting?

Many JavaScript frameworks bundle all dependencies into one large file. This makes it easy to add your JavaScript to an HTML page. The bundle requires only one link tag, and there are fewer calls needed to set up the page since all the JavaScript is in one place. In theory, bundling JavaScript in this way should speed up page loads and lower the amount of traffic that page needs to handle.

Code splitting is a common practice in large React applications, and the increase in speed it provides can determine whether a user continues using a web application or leaves. Many studies have shown that pages have less than three seconds to make an impression with users, so shaving off even fractions of a second could be significant. Therefore, aiming for three seconds or less of load time is ideal.

Popular module bundlers like webpack, allow you to split your bundles using dynamic imports. For example;

// math.js
export function subtract(a, b) {
  return a - b;
}

Without Code Splitting

//index.js
import { subtract } from './math.js';

console.log(subtract(26, 16)); // 10

With Code Splitting

import("./math").then(math => {
  console.log(math.subtract(26, 16)); //10
});

When Webpack comes across this syntax, it automatically starts code-splitting your app. If youโ€™re using Create React App, this is already configured for you and you can start using it immediately. Itโ€™s also supported out of the box in Next.js.

How does Code Splitting work In React

There are several ways to implement code splitting in React. Different bundlers work in different ways, but React has multiple methods to customize bundling regardless of the bundler used.

Using Dynamic Imports

The Dynamic import syntax works for both static site generation and server-side rendering. Dynamic imports use the then function to import only the code that is needed. Any call to the imported code must be inside that function (as seen above and below).

import("./math").then(math => {
  console.log(math.subtract(26, 16)); //10
});

Using React Lazy

React.lazy allows for lazy loading of imports in many contexts. It is not yet available for server-side rendering, but its diversity of functions makes up for that. The React.lazy method makes it easy to code-split a React application on a component level using dynamic imports. However, there will always be a slight delay that users have to experience when a code-split component is being fetched over the network, so it's important to display a useful loading state. Using React.lazy with the Suspense component helps solve this problem.

//Without Suspense Fallback
import React, { lazy } from 'react';

const HeaderComponent = lazy(() => import('./HeaderComponent'));

const App = () => (
  <div>
    <HeaderComponent />
  </div>
)
//With Suspense Fallback
import React, { lazy, Suspense } from 'react';

const HeaderComponent = lazy(() => import('./HeaderComponent'));

const App = () => (
  <Suspense fallback={<div>Loading...</div>}>
    <HeaderComponent />
  </Suspense>
)
//Suspense Fallback With Multiple Components
import React, { lazy, Suspense } from 'react';

const HeaderComponent = lazy(() => import('./HeaderComponent'));
const FooterComponent = lazy(() => import('./FooterComponent'));


const App = () => (
  <Suspense fallback={<div>Loading...</div>}>
    <HeaderComponent />
    <FooterComponent />
  </Suspense>
)

Loading Failures

If any module fails to load, for example, due to network failure, we will get an error that can handle these errors with Error Boundaries. Once we have created the Error Boundary, we can use it anywhere above our lazy components to display an error state.

import React, { lazy, Suspense } from 'react';

const HeaderComponent = lazy(() => import('./HeaderComponent'));
const FooterComponent = lazy(() => import('./FooterComponent'));


class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {hasError: false};
  }

  static getDerivedStateFromError(error) {
    return {hasError: true};
  }

  render() {
    if (this.state.hasError) {
      return <p>Loading failed! Please reload.</p>;
    }

    return this.props.children;
  }
}

const App = () => (
  <ErrorBoundary>
    <Suspense fallback={<div>Loading</div>}>
      <HeaderComponent />
      <FooterComponent />
    </Suspense>
  </ErrorBoundary>
)

Conclusion

With the above resource, I hope you're better suited to optimize your React application and have what it takes to split a project.

If you are unsure where to begin applying code splitting to your React application, follow these steps:

  • Begin at the route level. Routes are the simplest way to identify points of your application that can be split. The React docs show how Suspense can be used along with react-router.
  • Identify any large components on a page on your site that only render on certain user interactions (like clicking a button). Splitting these components will minimize your JavaScript payloads.
  • Consider splitting anything else that is offscreen and not critical for the user.

Resources Used:

๐Ÿ‘‰๐Ÿพ Learn more about me
๐Ÿ‘‰๐Ÿพ Connect on LinkedIn
๐Ÿ‘‰๐Ÿพ Subscribe to my blog, let's feast

QOTD: Whatever is worth doing at all, is worth doing well

ย