Frontend Performance Optimization with Code Splitting using React.Lazy & Suspense šŸ”„

Yuvraj Pandey
7 min readJun 30, 2021
Image: www.toptal.com

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?

Client Side Rendering (CSR) (Image: www.rockcontent.com)

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

At a certain point, however, a bundle grows to a certain size at which the overhead of interpreting and executing the code slows the page load down instead of speeding it up. This critical point is different for every page, and you should test your pages to figure out where this is. There isnā€™t a general guideline ā€” it all relies on the dependencies which is being loaded.

Image: Crystallize.com

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, and 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 on users, so saving 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([])

Image : Crystallize.com

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 multiple separate chunks:

  • One responsible for the code that makes up our initial route
  • Other chunks 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

Image : www.telerik.com

React.lazy allows for lazy loading of imports in many contexts. The React.lazy function allows you to dynamically import a dependency and render that dependency as a component in a single line of code. The Lazy component should then be rendered inside Suspense Component which helps to reflect some fallback content meanwhile the lazy component loads.

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 />
<Suspense>
</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.

Thatā€™s it for this article hope you 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 as you feel like it.

You can follow me on Twitter, Github, LinkedIn, and Facebook.

Happy Coding šŸ‘Øā€šŸ’» šŸŽŠ.

--

--

Yuvraj Pandey

Working towards striking an impact using technology for making this world a better place to live in āœ