How Does Code Splitting and Lazy Loading Work in React?

Improving the Performance of Your React app using Lazy Loading

Ashutosh Kumar
Bits and Pieces

--

Improving React performance is often discussed in interviews, with Code Splitting and Lazy Loading being the key topics. But Before Diving Deep we need to understand two things:

  1. How Client-Side Rendering Works with React
  2. How Single Page Application is built and deployed in React

How Client-Side Rendering Works with React

Client-side rendering works by using JavaScript to dynamically create and modify the HTML and CSS on the page.

  1. An HTTP request is made to the server
  2. The server receives the request and responds with a minimal HTML page to the client or a page with a loader along with a bundle.js asset
  3. The Browser(Client) loads the HTML page and executes the bundle.js code
  4. The javascript code then modifies the DOM which renders the final HTML page to the Client

This javascript code can make API requests to fetch the data and then load to the HTML and re-render the page in the browser

This is the foundation of creating single-page applications in React where the javascript file — bundle.js loads all the contents of the App. This leads to faster page transitions, as the page does not refresh when moving from one page to another. It also reduces the number of page requests made to the server.

How a Single Page Application is Built and Deployed

When we create a react app with npx create-react-app or vite we get a boilerplate code with src/App.js file inside where we can start writing our code and build react components.

The boilerplate code comes with many packages already installed like react, react-dom, and most importantly webpack. React uses build tools like Webpack to bundle all our code in a minified format efficiently.

Bundling is the process of following imported files and merging them into a single file — bundle.js

Photo by RoseBox رز باکس on Unsplash

This javascript bundle file is included with the index.html file as an asset to load our App(Components that we wrote with the JSX syntax in React with all packages). This process(bundling) happens because of our end goal to achieve Client-side rendering through React.

Note: You can find the index.html in the public folder where its content is just an empty div.

How to generate the bundle files in React?

When we have completed our React App the next step is to build it using the command — npm run build.This will generate a build folder with an HTML file with an empty div and a JS file called main.[hash].js which contains the App code to load onto the HTML and [number].[hash].js which contains the vendor code — modules that you’ve imported from within node_modules.

The javascript bundle is split between the main file and multiple chunk files that the browser will download

But Why is there no bundle.js file as mentioned earlier?

These multiple bundle files or chunks are a type of code-splitting happening through webpack behind the scenes when we created our react app through the create-react-app command

If you go to the webpack.config.js files inside node_modules/react-scripts/config you will find below the lines of code

output: {
// The build folder.
path: paths.appBuild,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext]',

publicPath: paths.publicUrlOrPath,

devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
}

If you see the code — isEnvDevelopment && ‘static/js/bundle.js . For the development mode, it converts all the code into a bundle.js file, but when we execute the command npm run build it creates a build directory with a production build of your app along with code-splitting.

React Build Process

What happens during the build process is that the React code written in JSX gets converted to these bundle file which is loaded as an asset for the index.html page.

Note: You can find the index.html in the public folder where its content is just an empty div.

These files are then moved to the directory of the remote server which listens for Web Page requests. Another way to deploy is by uploading the build folder using Third-party Hosting Providers like Github Pages and Netlify or Cloud Services like AWS Elastic Beanstalk and Azure App Services.

The bundle.js will contain the packages we imported and the Components we have coded with CSS files but as our app grows, our bundle will grow too with all our third-party libraries imported. This is where the problem begins.

Disadvantage of Client Side Rendering Process in React

When you are running a React App or viewing a Single Page Application ever noticed a blank page or a loader at start ?

This is because the Browser receives the blank HTML page at first, waits for the bundle.js to load, and executes it to load all the contents, and after that, when we route across pages, it’s fast. The bundle will contain all the data — images, pages, and API calls. This is a time-consuming task to wait for the bundle.js file to load especially if the size is large.

When Working with a Large React App with lots of Pages and Components the bundle size becomes large and it doesn’t make any sense to load all the contents on the client.

Photo by Elisa Ventur on Unsplash

Let’s take the case of an e-commerce app, there’s no need to load pages such as the checkout and wishlist pages when the client only visits the home page.

This is where Code Splitting and Lazy Loading come in. We can split the bundle and divide our App and only load certain pages/components when required.

Example Use Cases

  • Only Load the Expensive Image Gallery Component when a user scrolls to that Component
  • Only Load the page when a user requests the page route
  • Divide the page into separate bundles to load it faster.
  • Separate and split the vendor packages and cache it as a separate package as it is unlikely to be modified

Let’s Explore how to implement Lazy Loading on a Component first

Lazy Loading in React

Lazy Loading is a technique to speed up the webpage load times by delaying the loading of certain elements. Rendering content on a webpage is postponed until it is needed by the client browser.

In React all the Components are bundled into a single file, through lazy loading we can make specific components into their own chunk(out of the main bundle) and load them when required.

You can use the React.lazy function to load a module dynamically and render it as a regular component.

Let’s take an example React App as shown below, It has two components Dashboard and when we scroll down we see an Image Gallery Component.

import React from 'react'
import Dashboard from './Dashboard'
import Gallery from './Gallery'

function Home() {
return (
<div>
<Dashboard />
<Gallery />
</div>
)
}

export default Home

The image Gallery Component has a lot of images and is time-consuming for the Home Component to load, Let’s use React.lazy to lazy load the Image Gallery Component (not part of the main chunk).

Before:

import Gallery from "./Gallery";

After:

const Gallery = React.lazy(() => import('./Gallery'));

There is another step after this where we wrap the Gallery Component inside React.Suspense so that it has a fallback while the chunk file is loading asynchronously in the background.

We will discuss more about React.Suspense in m next article

<Suspense fallback={<div>Loading...</div>}>
<Gallery />
</Suspense>

You can see the results below, the Image Gallery Component is split now into separate chunks and loaded asynchronously.

Code Splitting in React

In React we use React Router to transition through different routes and load the Pages. We can lazy load these routes the same way we lazy loaded the components above. This will split our app into smaller chunks that only load when the corresponding route is accessed. This technique is called Route based Code Splitting

import React, { Suspense } from "react";
import { Routes, Route } from "react-router-dom";

import Home from "./Components/Home";
import './App.css'

const About = React.lazy(() => import('./Components/About'));
const Login = React.lazy(() => import('./Components/Login'));


function App() {

return (
<div>
<Suspense fallback={<div>Loading Page...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/login" element={<Login />} />
</Routes>
</Suspense>
</div>
);
}

export default App;

If you run the build command npm run build you can see our App split into different chunks with one main chunk and other async chunks which will load when required.

You can visit the below Github repository for the Complete Code

Wrapping Up

This article aimed to explore lazy loading in React and understand the benefits of doing so. I hope you found this article helpful.

Thank you for reading.

--

--

Self-taught Developer | Tech Blogger | Aim to Help Upcoming Software Developers in their Journey