Frontend Performance Optimization with Code Splitting using React.Lazy & Suspense 🔥

Frontend performance is important. JavaScript, though written to be a simple language, can produce surprisingly complex code bases making it difficult to scale up. Part of the reason is that there is a wide variety of classes and modules available. Most substantial JavaScript programs and frameworks have many dependencies, which can make a seemingly simple project embed a large amount of code quickly.

The more code a project has, the slower the browser will load. Therefore, you often have to balance the size of your dependencies with the performance you expect out of your JavaScript. Code splitting is a useful way to strike this balance.

What is Code Splitting?

The key to code splitting is figuring out which parts of a page need to use different JavaScript dependencies. Code splitting allows you to strategically remove certain dependencies from bundles, then insert them only where they are needed. Instead of sending all the JavaScript that makes up the application as soon as the first page is loaded, splitting the JavaScript into multiple chunks improves page performance by a huge margin.

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.

Split and Reduce your Bundles

Get rid of anything that takes up too much space. See if there are more lightweight alternatives for the libraries you are using. Using moment.js ? Try out date-fns. Using lodash? Try out lodash-es. Make sure you import only the individual parts that you actually use:

✅ Do …

import find from 'lodash/find'; find([])

❌ Don't …

import _ from 'lodash'; _.find([])

How does code splitting work in React?

Different bundlers work in different ways, but React has multiple methods to customize bundling regardless of the bundler used.

Dynamic imports

Perhaps the simplest way to split code in React is with the dynamic "import" syntax. Some bundlers can parse dynamic import statements natively, while others require some configuration. 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.

import("./parseText").then(parseText => {
  console.log(parseText.count("This is a text string", "text"));
});

The single bundle used in the application can be split into two separate chunks:
One responsible for the code that makes up our initial route
A secondary chunk that contains our unused code

With the use of dynamic imports, a secondary chunk can be lazy loaded, or loaded on demand. For eg, the code that makes up the chunk can be loaded only when the user presses the button or on execution of certain condition.

Using React.lazy

import React, { Suspense } from 'react';
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

The fallback prop can accept any element of React which will be rendered while waiting for the loading of the Component. The Suspense Component can be placed anywhere above the lazy component. Moreover, multiple lazy components can be wrapped with a single Suspense Component.

import React, { Suspense } from 'react';
const ComponentOne = React.lazy(() => import('./ComponentOne'));
const ComponentTwo = React.lazy(() => import('./ComponentTwo'));
function MyComponent() {
   return (
      <div><Suspense fallback={<div>Loading...</div>}>
         <ComponentOne />
         <ComponentTwo />
      </div>
   );
}

Route based code splitting: It can be difficult to implement code-splitting in code, the bundles can be split evenly, which will improve the experience for the user.

import React from 'react';
import Suspense from 'react';
import lazy from 'react';
import {Route, Switch, BrowserRouter } from 'react-router-dom';
const HomeComponent = lazy(() => import('./routes/HomeComponent'));
const BlogComponent = lazy(() => import('./routes/BlogComponent'));
const App = () => (
  <Suspense fallback={<div>Loading...</div>}>
    <BrowserRouter> 
      <Switch>
         <Route path={"/home"}>
            <HomeComponent />
         </Route>
         <Route path={"/blog"}>
            <BlogComponent />
         </Route>
         <Route path="/">
            <Redirect to={"/home"} />
         </Route>
      </Switch> 
    </BrowserRouter>
  <Suspense/>
);

Named Exports

React.lazy currently supports only default exports. An intermediate module that re-exports as default has to be created if one wants to import a module that uses named exports. This ensures the working of tree shaking and prevents the pulling in of unused components.

// Components.js
export const Component = /* ... */;
export const UnusedComponent = /* ... */;
// Component.js
export { Component as default } from "./Components.js";
As both React.lazy and Suspense are not available for rendering on the server yet now, it is recommended to use https://github.com/gregberge/loadable-components for code-splitting in a server-rendered app (SSR). React.lazy is helpful for rendering dynamic import as a regular component in client-rendered app (CSR).
Magic Comment at import()
import(
  /* webpackChunkName: "test", webpackPrefetch: true */
  "LoginModal"
)
// or
import(
  /* webpackChunkName: "test" */
  /* webpackPrefetch: true */
  "LoginModal"
)
// spacing optional
"webpackChunkName" : Using this magic comment we can set name for the js chunk that is loaded on demand.

Prefetch in Webpack

import(/* webpackPrefetch: true */ "...")

This "Resource Hint" tells the browser that this is a resource that is probably needed for some navigation in the future.
Browsers usually fetch this resource when they are in idle state. After fetched the resource sits ready in the HTTP cache to fulfill future requests. Multiple prefetch hints queue up and are fetched while idling. When leaving idle state while prefetching to browser may cancel any ongoing fetch (and put the partial response into cache, to be continued with Content-Range headers) and stop processing the prefetch queue.
To sum it up: Fetch while idle.

Preload in Webpack

import(/* webpackPreload: true */ "...")

This "Resource Hint" tells the browser that this is a resource that is definitely needed for this navigation, but will be discovered later. Chrome even prints a warning when the resource isn't used 3 seconds after load.
Browsers usually fetch this resource with medium priority (not layout-blocking).
To sum it up: Fetch like normal, just earlier discovered.

You may see a linting error that says:
Parsing error: 'import' and 'export' may only appear at the top level.
This is due to the fact that the dynamic import syntax is still in the proposal stage and has not been finalized. Although webpack already supports it, the settings for ESLint (a JavaScript linting utility) used by Glitch has not been updated to include this syntax yet, but it still works!

That's it for this article hope you would have learned something useful from it. So If you have any thoughts or suggestions, feel free to leave a comment below. Don't forget to share your love by clapping for this article as many times you feel like.
You can follow me on Twitter, Github , LinkedIn , Facebook.
Happy Coding 👨‍💻 🎊.

9