Code Splitting for React
Prerequisite
Familiarity with React Router
Proficiency in using DevTools, preferably in Chrome.
Installation of the latest version of Node is required.
Understanding of React Suspense concept.
Introduction
When leveraging React to create your high-quality application, the anticipation is for enhanced speed compared to traditional JavaScript development. However, if your React app encounters sluggish performance, a straightforward and library-light approach to address this is through code splitting.
This article delves into the concept of code splitting, elucidating what it entails and guiding you on its implementation to optimize the efficiency of your applications. Explore the benefits of code splitting and learn how to seamlessly integrate it into your React projects for improved performance.
Goals
This article aims to:
Describe the process of code splitting.
Provide a clear understanding of what code splitting is in the context of React applications.
Offer practical insights into implementing code splitting in React applications.
Highlight the advantages and benefits of incorporating code splitting into React applications.
Outline
Understanding Code Splitting
- Implementing Code Splitting
Benefits of code splitting
Part 1: Building the Initial Codebase
Setting Up
Building the Web App
Part 2: Identifying Bottlenecks
Part 3: Code Splitting with Lazy loading
Understanding Code Splitting
In the realm of software development, a cardinal objective is to craft applications that are not only well-optimized but also efficient and high-performing. Despite its relatively low profile in developer discussions, code splitting has garnered increased attention, particularly with the surge of web applications and the imperative to enhance user experiences.
At its core, code splitting entails the dissection of a software application into modularized components, leading to tangible benefits such as improved performance and expedited load times.
In a more succinct definition, code splitting is a technique that strategically dissects an application's codebase into smaller, more manageable parts. The primary goal is to load only the code essential for the current user's interaction, thereby mitigating the initial load time of the application.
In today's digital landscape, where users demand swift and responsive interfaces, understanding and implementing code splitting is imperative. Failing to do so could result in the loss of customers and, consequently, a negative impact on business.
When developers build applications, a common practice involves bundling all JavaScript code into sizable files. While this method offers simplicity and facilitates easy deployment, it can lead to substantial file sizes that, in turn, hinder the initial page load and overall web app interactivity.
Implementing Code Splitting
Code splitting can be implemented in various development environments and technologies.
Here are some common methods:
Webpack: If you're using Webpack as your build tool, you can easily implement code splitting using its dynamic import feature. Webpack will automatically split your code into smaller chunks and load them on-demand when needed.
React: React, a popular JavaScript library for building user interfaces, provides a built-in mechanism for code splitting using the
React.lazy()
andSuspense
APIs. These allow you to load components asynchronously as users navigate your application, and will be our focus for today's article.Vue.js: Vue.js, another popular JavaScript framework, supports code splitting through dynamic imports, which lets you load components on-demand, improving the performance of your Vue applications.
Server-Side Rendering (SSR): In server-side rendered applications, you can implement code splitting by dynamically importing modules to reduce the initial server response size and improve the time-to-interactive metric.
Benefits of Code Splitting
Some worthwhile benefits of code splitting includes:
Faster Initial Loading Time: Code splitting enables the loading of of only the essential code required for the initial page view, with this, users don't have to wait for the application to become fully usable.
Improved User Experience: Faster load times contribute to a better user experience, which leads to better satisfaction and engagement.
Lower Bandwith Consumption: Smaller code chunks mean the amount of data transferred over the network is lesser.This reduces bandwidth and potentially saves costs for both users and developers.
Efficient caching: Code splitting can improve browser caching as the only the changed or updated code chunks need to be loaded,reducing the need to download the entire application code each time.
Better Maintainability: Breaking the application into more modular parts can make the codebase more manageable and easier to maintain, test and debug.
To illustrate the concept of code splitting in React today, we'll build a demo web app featuring simple toggle and alert functions. This demo will unfold in three major stages. Firstly, we'll set up our React environment using ViteJS and code up the web app.
Afterward, we'll inspect the web app using a browser developer tool—preferably Chrome—to identify any inefficiencies. This critical analysis will help us understand the initial performance bottlenecks.
Following the inspection, we'll make significant changes to different components of the web app to implement code splitting. By optimizing the loading of components on-demand, we aim to observe tangible improvements in the app's overall efficiency.
Part 1: Building the Initial Codebase
Since this is a very practical topic, I felt it would be best to orient this article to actually make a litle demo project that mirrors a standard react codebase and then dissect the project while using codesplitting to make the project better in terms of efficiency.
Setting up
Let's set up the ViteJS environment.
- Before we begin, ensure that you have the latest version of Node installed on your computer. As of the time of writing this article, the latest version is 21.1.0. Verify your Node installation by running the following command in your terminal:
node --version
- Following the Node installation, let's set up the React working environment. Execute the following command in your terminal to install Vite globally:
npm install -g create-vite
- Switch to the directory where you intend to create the web app. Use the following command to navigate to your desired directory:
cd project-location
- Replace
project-location
with the location you want to make the app.
- Once you're inside the desired location, run the following command to create the
Vite-React
project:
npm create vite@latest
- You'll be prompted to provide information about your project. Feel free to name it as you wish; for instance, I'll name mine
split-app
. Enter the relevant details when prompted.
- When prompted about the framework, choose "react," and then select the "JavaScript" option. This ensures the project is set up with React using JavaScript.
NOTE: If you encounter challenges while selecting options using Git Bash, it's recommended to use an Integrated Development Environment (IDE) terminal such as VSCode for a smoother experience.
- Execute all the commands displayed to install the necessary dependencies. This ensures that your project has all the required packages and dependencies installed.
cd split-app
npm install
npm run dev
- Running the command
npm run dev
will initiate a visual output on a specific port, confirming that the Vite-React environment is ready for use. This visual output typically indicates that your development server is up and running.
Double click on the port.
Here's the result
Making the Web App.
Now, let's start building the app. At this point, the essential packages should be installed.
- An important package we'll utilize is React Router, facilitating the behavior of our web-page as a Single Page Application (SPA). To install this package, use the following terminal command:
npm install react-router-dom@6
- Another crucial package will be required later for error handling. Use this command to install:
npm i react-error-boundary
- Once the packages are installed, delete the default files in the
/src
folder of your app and ensure it has the following file and folder arrangement:
/src
/components
Admin.jsx
AdminHello.jsx
ErrorFallback.jsx
Header.jsx
Home.jsx
Layout.jsx
App.jsx
index.css
main.jsx
These components will do these things:
Admin.jsx
: The main component for the admin section.AdminHello.jsx
: A component for greeting.Header.jsx
: A component for the header section.ErrorFallback.jsx
: Displays error messages.App.jsx
: The combined component for your React application.index.jsx
: Contains global styles for the entire application.main.jsx
: The main entry point where the React application is initialized and rendered into the HTML root element.
Next, let's populate all the files with code. To keep the article succinct, I won't explain every line of code but will provide a generalized idea of what each file's code will accomplish.
- Now, let's create the main components, starting with /components/Admin.jsx.
import AdminHello from "./AdminHello";
const Admin = () => {
return (
<main className="admin">
<AdminHello />
<h2>Author</h2>
<p>
Debitis culpa iste perspiciatis quos odit consequatur dicta nobis?
Veritatis temporibus facere, quibusdam obcaecati molestiae possimus
repellendus ratione? Rerum harum esse eius doloribus distinctio a neque
sed dolore saepe voluptatem odit eligendi debitis quas molestiae ullam
assumenda, fuga exercitationem quasi repudiandae excepturi. Ipsum, ex
commodi. Odio minus adipisci voluptas assumenda possimus a magni
necessitatibus libero. Quis alias dolorum dolor odio doloremque qui
adipisci molestias eveniet eaque, consequuntur voluptatem numquam,
aperiam beatae tenetur exercitationem cupiditate, necessitatibus nobis
ea tempora ex. Tempore, nobis ut rem accusamus deleniti aperiam, vero
dignissimos at, unde error sapiente eaque. At enim laudantium autem unde
mollitia blanditiis?
</p>
<p>
Reiciendis totam, repellendus maiores suscipit, iure unde in praesentium
voluptatem nobis enim provident illum cumque at molestias, ratione animi
qui possimus deserunt. Harum repudiandae excepturi beatae explicabo eius
quis nemo eum sapiente temporibus a non debitis velit, optio distinctio
consequuntur repellat cumque, ab possimus voluptate saepe perspiciatis
quam. Quam aliquam, quaerat esse porro, tenetur, dolorem provident
distinctio vero nostrum ab obcaecati? Amet voluptate animi tenetur
praesentium ullam saepe iste a. Explicabo, distinctio temporibus!
Laudantium quod reprehenderit dignissimos voluptates animi repudiandae,
ad perferendis dolore a, rem voluptate repellat quasi assumenda magnam
eaque. Sit dignissimos libero atque officiis doloribus deserunt. Eos,
architecto!
</p>
<p>
Tenetur, sed sequi officiis at dolor, suscipit similique esse molestiae
ea animi consectetur aliquid facere itaque provident quasi. Adipisci
ipsum eos odio harum necessitatibus quas corporis reprehenderit
perspiciatis unde natus labore architecto libero saepe eaque, vero
itaque distinctio exercitationem. Tempora qui sequi maxime dolorum
perferendis neque minus quaerat molestias eius, libero incidunt pariatur
similique officia ea? Eius sunt tenetur consequatur vitae, earum maxime
assumenda dicta vero. Neque doloremque dignissimos saepe, deserunt
officiis nam labore non rem vero, animi dolores quae magnam.
Reprehenderit provident molestias eaque, suscipit sapiente dolores nemo
excepturi iste nobis fugiat distinctio iure! Eius ad hic perferendis
explicabo?
</p>
<p>
Perspiciatis accusantium quo optio repudiandae dolorem repellat, est
laborum adipisci quas ipsum ad explicabo dolorum iusto quos, similique
numquam iure? Perspiciatis perferendis architecto, fugit, veritatis,
voluptatum eos officiis autem distinctio delectus cupiditate et ea quas
laborum necessitatibus fuga voluptate! Nihil, sapiente? Eaque libero
exercitationem cumque porro, dolorum ab deserunt eius corporis dicta
magni impedit incidunt nam, laudantium tenetur tempora explicabo
consequuntur deleniti possimus accusantium ad non atque dolorem
officiis. Voluptatibus cum neque alias eveniet? At asperiores molestias
impedit illo nam, architecto magnam perspiciatis neque eaque fugiat
tempora, sequi natus laboriosam totam, unde eum repellendus rerum odio.
Sequi, asperiores commodi. Expedita.
</p>
<p>
Esse ad voluptatibus libero officiis optio, beatae accusamus dolorem
consequatur consectetur qui unde in assumenda! Eius, veritatis.
Reiciendis hic sed placeat cumque dolor maxime eius esse molestias
dolorem, sequi voluptatum, aliquam sapiente debitis. Hic labore dolorum
deleniti quo soluta, laudantium aliquid sed! Totam laborum aspernatur
nulla perferendis ut assumenda numquam. Maiores sunt consectetur
deserunt dolorum dignissimos quidem cupiditate pariatur accusantium
saepe, ipsa corporis voluptatibus perspiciatis, alias nostrum minus
molestias suscipit esse? Voluptatem id quae magni ipsum aperiam iure,
aliquam saepe maxime inventore adipisci aliquid perspiciatis ea quas?
Dolorum id mollitia, aperiam quos magnam perferendis officia doloremque
dolores, placeat ipsa ab?
</p>
<p>
Expedita veniam, earum mollitia error iste adipisci nemo asperiores
veritatis velit facilis amet provident accusantium commodi quasi nisi,
fuga sed, dicta deserunt minima dolore eos corrupti! Vitae error,
accusamus modi facilis omnis atque repellendus inventore id sed mollitia
quidem corrupti quibusdam labore ea quia iste voluptate sit repudiandae
quod aliquid doloremque eum nesciunt possimus eveniet! Nisi itaque ab
quibusdam sunt fugiat praesentium ipsum, doloribus consequuntur harum
neque? Debitis placeat, unde aspernatur laborum excepturi eum sapiente
consequatur necessitatibus? Nulla sequi atque placeat unde illum
accusamus ipsam, reiciendis tenetur delectus ducimus laborum cumque quia
sit laboriosam quibusdam necessitatibus, voluptatem fuga id itaque!
</p>
</main>
);
};
export default Admin;
- Next, let's work on
/components/AdminHello.jsx
.
const AdminHello = () => {
return <h3>Hello, Welcome to Open Replay</h3>;
};
export default AdminHello;
- This renders another page component.
- Up next
/components/ErrorFallback.jsx
.
const ErrorFallback = ({ error, resetErrorBoundary }) => {
return (
<div className="error">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
};
export default ErrorFallback;
- This is the error fall back messsage.
- Next up,
/components/Header.jsx
.
const Header = () => {
return (
<header>
<h1>Open Replay Blog</h1>
</header>
);
};
export default Header;
- This is the header component.
- For the home page component,
/components/Home.jsx
.
import { Link } from "react-router-dom";
const Home = () => {
return (
<main className="home">
<h2>Open Replay</h2>
<p>
<Link to="/admin">Read more on Open Replay</Link>
</p>
<p>
<b>Chief Editor :</b> Federico Kereki
</p>
</main>
);
};
export default Home;
- Certainly, let's add code to
/components/Layout.jsx
to ensure a proper layout.
import { Outlet } from "react-router-dom";
import Header from "./Header";
const Layout = () => {
return (
<div className="App">
<Header />
<Outlet />
</div>
);
};
export default Layout;
- Moving on to the main application files, let's start with
/src/App.jsx
.
import { Routes, Route } from "react-router-dom";
import Home from "./components/Home";
import Layout from "./components/Layout";
import Admin from "./components/Admin";
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="admin" element={<Admin />} />
</Route>
</Routes>
);
}
export default App;
- This defines routes for the application.
/src/index.css
provides overall styling.
@import url("https://fonts.googleapis.com/css2?family=Poppins&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--ADMIN-BG: #fdf7e4;
--HOME-BG: #faeed1;
--HEADER-COLOR: #ded0b6;
--HEADER-BG: #bbab8c;
}
body {
min-height: 100vh;
font-family: "Poppins", sans-serif;
}
#root,
main {
min-height: 100vh;
}
main {
display: grid;
place-content: center;
padding: 1rem;
}
header {
background-color: var(--HEADER-BG);
color: var(--HEADER-COLOR);
position: sticky;
top: 0;
padding: 1rem;
z-index: 1;
}
.home {
background-color: var(--HOME-BG);
text-align: center;
}
.admin {
background-color: var(--ADMIN-BG);
}
.admin p {
margin-bottom: 1rem;
}
/src/main.jsx
gets loaded into the HTML document.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/*" element={<App />} />
</Routes>
</BrowserRouter>
</React.StrictMode>,
);
- This contains the main
react-router
code.
- After making all these changes, run the following command in your terminal and click on the local host link provided.
npm run dev
- This concludes the process, resulting in a simple homepage and a link that, when clicked, expands to reveal more content.
Part 2: Identifying Bottlenecks
In my perspective, a pragmatic software engineer doesn't categorize software as strictly right or wrong but rather on a spectrum of efficiency. While there's nothing inherently flawed with the demo as it functions perfectly, its efficiency might be optimized further.
The crux of our discussion in this article is that if the client doesn't require the full code at a specific time, it's not best practice to provide everything at once. It's crucial to deliver only the necessary code to the client at a given point, emphasizing the importance of code splitting for enhanced efficiency and a more streamlined application.
We want to address an issue with the split-app
, our example React app. Currently, upon loading, all components, including /components/Admin.jsx
and /components/AdminHello.jsx
, are being loaded. However, these components are intended to be loaded only when an admin is using the app, not on the initial load.
Our objective with code splitting is to prevent the loading of the admin components until the "read more" link is clicked. To visualize these inefficiencies, let's turn to DevTools:
- Open chrome developer tools by either right-clicking on the demo and choosing "inspect" or option or pressing Option + ⌘ + J (on macOS), or Shift + CTRL + J (on Windows/Linux).
- Click on the "Sources" option, and right-click for a moment on the refresh option. Then, select "Empty Cache and Hard Reload." This ensures a fresh start, clearing any cached data that might affect the loading behavior of the application.
- After refreshing the page, navigate to the
src
folder and then to thecomponents
folder. Observe the files, and you'll notice that bothAdmin.jsx
andAdminHello.jsx
are present. As you are aware, these files should only be loaded on demand, highlighting the current inefficiency we aim to address with code splitting.
Part 3: Code Splitting with Lazy Loading
Now, let's optimize our application. Keep in mind that the objective of this demo is to ensure that the files /src/components/Admin.jsx
and /src/components/AdminHello.jsx
are loaded only when the "read more" link is clicked. This is the essence of code splitting—to load specific components on-demand for a more efficient application. Turn over to /src/App.jsx
because that is the file where we would be splitting the application. And make changes to the file like this:
import { Routes, Route, useNavigate } from "react-router-dom";
import Home from "./components/Home";
import Layout from "./components/Layout";
// import Admin from "./components/Admin"
import { ErrorBoundary } from "react-error-boundary";
import ErrorFallback from "./components/ErrorFallback";
import { lazy, Suspense } from "react";
const Admin = lazy(() => import("./components/Admin"));
function App() {
const navigate = useNavigate();
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route
path="admin"
element={
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => navigate("/")}
>
<Suspense fallback>
<Admin />
</Suspense>
</ErrorBoundary>
}
/>
</Route>
</Routes>
);
}
export default App;
In the previous version of this file, <Route path="admin" element={<Admin />}/>
is responsible for handling and granting access to the admin page.
To ensure that the admin pages are loaded in the browser only when the admin link is clicked, here are the changes made in src/App.jsx
:
import { Routes, Route, useNavigate } from "react-router-dom"
: This line imports specific components and functions from thereact-router-dom
library for setting up routing in the application.The newly added import statements
import { ErrorBoundary } from "react-error-boundary"
andimport ErrorFallback from "./components/ErrorFallback"
are part of React's system for catching errors anywhere in the component tree and logging those errors.Meanwhile,
import { lazy, Suspense } from "react"
are modules used for lazy loading and code splitting. Lazy loading defers the loading of certain assets until they are needed.This line
const Admin = lazy(() => import('./components/Admin'))
uses thelazy
module from React to lazy load theAdmin
component.This component
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => navigate('/')}>
is the standard way of using theErrorBoundary
andErrorFallback
modules to handle errors. Thenavigate
function redirects to the home page in case of errors.<Suspense fallback>
uses theSuspense
module from React to enable lazy loading. Thefallback
prop is required and represents the UI to show while the main UI components are being lazy loaded. However, since it isn't in the scope of this article, I chose to not create a proper fallback UI.
To confirm if our changes worked:
Exit your current thread on your terminal using CTRL+C. Save your changes.
Restart the server with this command, and click on the port:
npm run dev
- Open developer tools, go to the "Sources" option, and refresh with the "Empty Cache and Hard Reload" option.
- Toggle the
src
folder in DevTools, and observe that the admin files/components/Admin.jsx
and/components/AdminHello.jsx
are absent. This behavior occurs due to lazy loading.
- Now, if you click the link on the homepage,
/components/Admin.jsx
and/components/AdminHello.jsx
will be loaded in the browser.
Imagine if this were a React codebase with millions of lines of code. Consider the time it would take to load all that code at once to the client and the impact on user experience.
This my friend, is the magic of code splitting. With code splitting, you can selectively deliver code to users based on their interaction with your web application, optimizing performance and providing a more responsive user experience.
Conclusion
This article has unraveled a powerful technique for enhancing the efficiency of React applications.
We embarked on a comprehensive exploration, aiming to provide a clear understanding of what code splitting entails in the context of React development.
The goals included describing the process of code splitting, highlighting its advantages,and offering practical insights into its implementation.
By breaking down the complexities of code splitting, we've illustrated its potential to transform React applications, especially in scenarios where loading large volumes of code upfront could hinder user experience.
The practical demo, focusing on a web app example, allowed us to witness firsthand how code splitting can be a game-changer. Through strategic implementation, we ensured that components are loaded only when necessary, optimizing performance.
Code splitting isn't just a technique; it's a solution to the challenge of delivering large-scale applications without sacrificing speed. As we conclude this exploration, consider incorporating code splitting into your React projects, unlocking the magic of efficient and responsive user experiences.
Resources
Subscribe to my newsletter
Read articles from Adaeze Uka directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Adaeze Uka
Adaeze Uka
A passionate beautiful and curious beaut from Africa, I can't wait to see what the world of programming has to offer