Optimize large React app performance by code-splitting

Saeloun Logo

In this blog, we will try to understand
what is code splitting
and why we should implement it.
With Code splitting, we can reduce the initial bundle size,
app loading time, and enhance its performance.

What is Bundling?

When we create an application, we write code in several files.
It includes many modules
and third-party libraries.
Bundling is a way to convert these many files into
a single large file(bundle), well not literally,
but for understanding, yes.
And this bundle is used by a web browser to load the application.

Initially, when our application is small,
the bundle size is small but
as the application grows
and becomes more complex,
the bundle size grows simultaneously.
And the bigger the bundle size is,
the more time it takes for the application to load.
Therefore, we can say that bigger bundle size is an issue
and affects the application performance.

Now, what if there is a definite way to load the necessary files to
start the application
and later load other files,
as well, when required.
This way, we can keep our initial bundle size small
and get our applications to load faster.

React has this feature, It is called Code-Splitting.

What is code splitting?

Code splitting is a feature that helps in splitting the code
or components into various bundles
and load them on demand or parallel.
Let’s see how we can implement code splitting in
our application.

Using dynamic import()

Whenever we import a module or third party library,
we generally import it like the example below –

import { renderProfile } from './profile';

In the above example,
we are importing the file synchronously.
It means that the initial bundle file will have this import.

Now, what does it mean that we can import files based on their requirements?

It means we should only import if the application demands them.
Here comes the dynamic import method.
It helps in importing files asynchronously.

import('./profile').then(profile => {

The dynamic import method returns promise and
that is why we can call .then
and .catch methods to handle the promise.

We can use the asynchronous import function to load files,
and modules.
The dynamic import works on both server-side
and client-side rendering.

Using React.lazy and React.Suspense

Another way of splitting the code is using the React.lazy() method.
This method helps in the lazy-loading of a component.
It means that we can define components that can be imported
dynamically in the application.
This helps us reduce the bundle size because we are delaying the
loading of the component that might be used later in the application.

Let’s understand this by an example.
Here, we will be creating a simple App.js file that has
some synchronous imports
and then we shall try to split the code.

import React, { useState } from 'react';
import ProjectIntro from './projectIntro';
import ProjectDetails from './projectDetails';

export default function App() {
  const [showDetails, setShowDetails] = useState(false);

  return (
        <h1>Project List</h1>
        <ProjectIntro />
        <button onClick={() => setShowDetails(true)}>Show Details</button>
        {showDetails ? <ProjectDetails /> : null }

Now, we can see in the above example that component ProjectDetails
will only render when the user clicks on the button Show Details.
So here we can use lazy loading to load the component ProjectDetails.

Let’s do it with the help of React.lazy().
This method takes a function that
calls a dynamic import().

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

But there is a catch, what if our import takes time to load?
The React.Suspense comes into the picture.

The lazy component is always used within the Suspense component.
It allows us to
specify loading indicator until our lazy component is ready to render.

import React, { useState, Suspense } from 'react';
import ProjectIntro from './projectIntro';

const ProjectDetails = React.lazy(() => import("./projectDetails"));

export default function App() {
  const [showDetails, setShowDetails] = useState(false);

  return (
        <h1>Project List</h1>
        <ProjectIntro />
        <button onClick={() => setShowDetails(true)}>Show Details</button>
        <Suspense fallback={<div>Loading...</div>}>
          {showDetails ? <ProjectDetails /> : null }

The fallback props take any React element that we want to show while the
lazy component is loading.

React.lazy() is not available for the server-side rendered apps yet.
React recommends
lodable components
for code-splitting in server rendered app.

Route Level Splitting

Now that we know how to split our code to reduce
the bundle size, it’s time to understand where can we do it in the application.
We should consider user experience while code-splitting so
that it doesn’t hinder the way the user interacts with the application.

Therefore, routes are a good place to start with because generally, users are
familiar with delays when they transit from one route to another.

Let’s see how does it work.

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from "./home";

const Profile = lazy(() => import('./profile'));
const ContactUs = lazy(() => import('./contact'));

const App = () => (
    <Suspense fallback={<div>Loading...</div>}>
        <Route path="/" element={<Home />} />
        <Route path="/profile" element={<Profile />} />
        <Route path="/contact" element={<ContactUs />} />

In the above example, we implemented lazy loading to
load the components Profile and ContactUs.
Now, these components will only get loaded when the user
will hit their respective routes.
Since the components are getting loaded dynamically, it
will not at all affect the initial bundle size.

To read more about code-splitting, please check official React documentation.

Source link

Leave a reply

Please enter your comment!
Please enter your name here