React lazy loading and performance

Ryan Lucas
Ryan Lucas
Head of Design @ Retool

Mar 14, 2022

This post was written with help from Pragati Verma.

One of the most talked about problems in full stack development right now is front end bloat. Standardization around React and Vue has (at least in part) resulted in massive code bundles, which can often increase the time it takes for an app to open and negatively impact the user experience.

React, the “leader” in this space at the moment, is not without blame here. Big and bloated components with unused code chunks are often bottlenecks in performance when being downloaded or executed in the browser.

One way to address this challenge is through lazy loading, an optimization technique where the loading of an item is delayed until it’s absolutely required. It works well for common UX components and patterns like dialogs, warnings, and notifications. These are only displayed after a user interaction or a specified event and, as such, don’t need to be loaded with the initial bundle (at least in theory).

For example, when you click on an Add to Cart button on an e-commerce site, it might open up a modal or a pop-up saying, “Added to cart successfully!” Because the pop-up component is not required when the web app is first downloaded by the browser, it may be code-split (i.e., separated from the main JS bundle) and lazy-loaded when the user adds something to the cart.

This article explains what lazy loading is, why you might want to use it, and how to build lazily-loaded components in React.

What is lazy loading?

As the name suggests, lazy loading is an optimization technique or a design pattern in which the loading of an item, whether it’s a picture, video, web page, music file, or document, is delayed until it is required, saving bandwidth and precious computing resources.

By default, React bundles the entire codebase and deploys it all at the same time. Normally, that’s fine because React single-page applications (SPAs) are tiny. But if you’re working with a more complex app like a content management system with a customer portal, loading the entire program right away isn’t ideal.

Before making a React application production-ready, it gets packed using preinstalled bundlers, such as webpack. When this packed project is loaded, it loads the whole source code at once, even pages that the user seldom visits.

Lazy loading was developed to thwart this behavior. It cheats the process, deferring the loading of the non-critical parts of an app and enabling them to be loaded on demand to reduce DOM load time and boost application performance. Users are then able to access a website even if everything hasn’t been downloaded.

The benefits of lazy loading

The primary advantages of lazy loading are performance related:

  • Faster initial loading: By reducing the page weight, lazy loading a web page allows for a faster initial page load time.
  • Less bandwidth consumption: Lazy-loaded images save data and bandwidth, particularly useful for individuals who don’t have fast internet or large data plans.
  • Preserving system resources: Lazy loading conserves server and client resources by requesting just a fraction of components.
  • Reduced work for the browser: When pictures are lazy-loaded, your browser does not need to process or decode them until they are requested by scrolling the page.

How to use lazy loading in React

React has two native features that make it really simple and easy to implement lazy loading—React.lazy() and React.Suspense.

Note that the following approach is intended for client-side-rendered (CSR) web projects that run all their code in a browser.

React.lazy()

The React.lazy() function allows you to render a dynamic import as a normal component. It makes it simple to construct components that are loaded dynamically yet rendered as regular components. When the component is rendered, the bundle containing it is automatically loaded.

React.lazy() accepts a method as an argument that must deliver a promise after loading the component with import(). The resolved promise points to a module with a default export that includes the React component. A key feature of React 16.6, this function also eliminates the requirement for third-party libraries like react-loadable.

Implementing React.lazy() looks like this:

1// without React.lazy()
2import OtherComponent from './OtherComponent';
3
4const MyComponent = () => (
5    <div>
6        <OtherComponent/>
7    </div>
8)
9
10// with React.lazy()
11const OtherComponent = React.lazy(() => import('./OtherComponent'));
12
13const MyComponent = () => (
14    <div>
15        <OtherComponent/>
16    </div>
17)
18

React.Suspense

A component built with React.lazy() is only loaded when it is required to be displayed. While the lazy component is loading, you should probably show some form of placeholder content, such as a loading indication.

React.Suspense is a component used to surround lazy components. You can use a single suspense component to encapsulate numerous lazy components at various hierarchy levels. Keep in mind that this is technically designated as experimental by the React team.

While all the lazy components are loaded, other React elements can be shown as placeholder content by passing a fallback prop to the suspense component. In a nutshell, it allows you to define the loading indicator if the components in the tree below it are not yet ready to render.

Here’s an example of the implementation of React.Suspense:

1import React, { Suspense } from "react";
2
3const LazyComponent = React.lazy(() => import('./OtherComponent'));
4
5const MyComponent = ( ) => (
6    <div>
7        <Suspense fallback={<div>Loading...</div>}>
8            <LazyComponent/>
9        </Suspense>
10    </div>
11)
12

Multiple lazy components can be placed inside the suspense component:

1import React, { Suspense } from "react";
2
3const LazyComponent1 = React.lazy(() => import("./OtherComponent1"));
4const LazyComponent2 = React.lazy(() => import("./OtherComponent2"));
5const LazyComponent3 = React.lazy(() => import("./OtherComponent3"));
6const LazyComponent4 = React.lazy(() => import("./OtherComponent4"));
7
8const MyComponent = () => (
9    <div>
10        <Suspense fallback={<div>Loading...</div>}>
11            <LazyComponent1 />
12            <LazyComponent2 />
13            <LazyComponent3 />
14            <LazyComponent4 />
15        </Suspense>
16    </div>
17);
18

Error handling for React.lazy()

As mentioned earlier, the import() function returns a promise when using React.lazy(). This promise can be rejected due to network failure, file not found errors, file path errors, and so forth.

To build a good user experience upon failure, you should place an error boundary around the lazy component:

1import React, { Suspense } from "react";
2
3const LazyComponent1 = React.lazy(() => import("./OtherComponent1"));
4const LazyComponent2 = React.lazy(() => import("./OtherComponent2"));
5const LazyComponent3 = React.lazy(() => import("./OtherComponent3"));
6const LazyComponent4 = React.lazy(() => import("./OtherComponent4"));
7import ErrorBoundary from "./error.boundary.js";
8
9const MyComponent = () => (
10    <div>
11        <ErrorBoundary>
12            <Suspense fallback={<div>Loading...</div>}>
13                <LazyComponent1 />
14                <LazyComponent2 />
15                <LazyComponent3 />
16                <LazyComponent4 />
17            </Suspense>
18        </ErrorBoundary>
19    </div>
20);
21

Lazy loading best practices

Keep the following best practices in mind while implementing lazy loading:

  • Lazy-load components in code only when they’re not necessary for the initial functionality or features in a website or web app.
  • Only lazy-load components below the fold or beyond the user’s initial viewport.
  • Use the decode() method in JavaScript to decode lazy-loading images asynchronously before adding them to the DOM.
  • Set proper error boundaries to handle errors that may arise in case the components fail to load when lazy loading.
  • Provide a noscript alternative for the components that are to be lazy-loaded for users who disable JavaScript in their browser or in cases when JavaScript is not available.

Conclusion

This article explained the basics of lazy loading, its benefits, and how to implement it in React using React.lazy() and React.Suspense.

Lazy loading is a great way to boost page performance while keeping users on your site. If used appropriately, it may help you build efficient and user-friendly solutions.

Reader

Ryan Lucas
Ryan Lucas
Head of Design @ Retool
Mar 14, 2022
Copied