When building single page applications, SPA. The application
load time performance is very important as this improves the user experience. As
development teams mostly focus on functional requirements, there is a tendency
to skip some of the non-function requirements like performance improvements.
The result is that when a web application is loaded, all the resources
including views that are not visible on the home page are downloaded in a
single bundle. This is referred as eagerly loading, and this approach often causes
a slow load time as all the resources need to be downloaded before the user can
interact with the application.
To avoid this performance issue, we want to only load the
resources that are needed at the time that the user is requesting it, on demand.
As an example, only load the resources for the home page without loading other
page resources, thus improving the load time. This is usually called lazy
loading. To support this, we need to load chunks of the application on demand. A chunk is basically a JavaScript or CSS file
that packages only the containers, components and dependencies that are needed
for that view to render.
To lazy load the different views for an application, we need
to implement the concept of Code Splitting, which basically enables us to split
the code bundle into chunks, so each container view and dependencies can be
downloaded only as the user is requesting it. This greatly improves the app
performance because the chunk size is small compared to the entire code bundle.
Importing Container Views and Routing
A simple yet very important approach to improve load time
performance is to lazy load the routes. This is a code split process, which
breaks down each container view into a separate chunk. In addition, components
within these containers can also be lazy loaded to break down further the size
of each chunk.
To get started, let’s look at what the navigation configuration
of React application looks like, so we can review what takes place when a user
loads the application.
In this example, we should notice that our React app has three main containers, which are basically the pages or views that the user can load from the app. These containers are usually in the container folders of the project file structure. This path is important because it is needed to associate them to a route.
👍 Pro Tip: It is a best practice to plan your folder structure and create a folder for each container, components, elements, and services.
To loads those views, we need to import them and map them to
an application route. This should be done on the application starting point,
which should be the App.tsx file. The code to do that looks like this:
In this code, we are using the import directives to load
each container view. Each of those views is then mapped to an application route
directive. When using import directives, there is no optimization, so we should
expect that when this app loads on the browser, all the views should be loaded in
a single bundle. To clearly see this, let’s use the browser dev tools to
inspect how this look at the network level.
By doing a network inspection, we can see that there is a
bundle.js file. This file has a 409kb size. In the example of a simple app,
this is not bad at all, but for real world apps, this bundle size may be much
bigger than that, and eventually it impacts the load time. A benefit of using a
single bundle is that there are no additional trips to download other file
chunks, but this approach will not let your application scale and perform
acceptably over time.
Lazy Loading Container Views
Now, we should be able to understand that as the app continuous
to grow, there is potential performance challenge, so the question is how can
be optimized the loading of our application? The simple answer is that we need
to Code Split the bundle into smaller chunks. A quick approach is to Lazy
Loading the routes. This should enable us to improve the load time with very
small code changes. Let modify our previous code and look at the performance
difference.
In the updated version of our code, we are now using the
lazy direct to delay the import of the container view only when the user
requests that route. The rest of the code remains the same because we are still
using the same container references and mapping them to a route. OK, let’s run
the app and do another network inspection, so we can really understand the improvement.
In this last trace, we can see there still a bundle file
with roughly the same size of the file as before. This bundle file contains the
optimization code to map a route to a particular bundle chunk. When a
particular route is loaded, home route is loaded by default, the chunk for that
view is downloaded, notice the src_container_Home_index_tsx.chunk.js. As
the user navigates to other routes, the additional chunks are downloaded on
demand, notice the Analytics and Admin chunks.
Final Thoughts
With this simple app, we may not be able to truly appreciate
the optimization that has been done by just deciding to lazy load the
containers. However, in real-world applications, the size of a single bundle
will quickly get big enough to impact the usability of the application as users
will have to wait a few or several seconds before the app is clickable. This is
referred to as Load Time.
In addition, build tools for framework like React show
performance warnings when loading the application in the development
environment, as it tracks some performance indicators like load time. Also, it
is a good practice to use a tool like Lighthouse, in the browser dev tools, to
run a report and measure performance indicators like load time, render time and
others.
👍 Pro Tip: Always use a performance tool to measure performance and other industry best practices for web applications.