The React JavaScript framework for Single Page Applications
(SPA), which can be hosted on Azure Static Web Apps (SWA) or CDN hosting,
supports the concept of Code Splitting by loading pages/routes dynamically
instead of building one single package.
The benefit of Code Splitting is that it enables for faster load time on
the user’s browser as opposed to loading the entire application on one single
request. This feature however also introduces other concerns that we need to
manage with code otherwise, the user can end up with a white page in front of
them as the application is unable to render the requested content.
To leverage Code Splitting, we load the page routes
by using React Lazy* Loading and Dynamic Import features. This way the page, or
chunk, for the selected route is loaded only when it is requested. Because this
is done a run-time, a new request is made to the server to download the chunk
of code that is needed to render the page. Yes, this is a server-side trip to
get the additional resources, and the application still works as an SPA.
Note: Lazy loading is
an application architecture that is used to load code in memory or web pages
only when it is needed. This improves application performance and load time.
Because a server-side request must be made to download an
additional chuck of code and render the route or page properly, there could be
failures that are reflected as the following errors:
Uncaught ChunkLoadError: Loading chunk 2 failed.(timeout) |
The failure could be due to two main reasons:
- There is a network latency issue and the content failed to download
- The client application may have been cached on the browser and App update has replaced those files with new hash codes
For the network problem, we can add some error handling and
retry logic to allow the application to get the chunk again. We do need to be
careful here and avoid locking the user on some retry logic because the second case,
app update, can be happening, in which case the only way to solve the issue is
by refreshing the entire app again. Let’s look at the code below and talk about
the root cause of this issue.
|
After looking at the code, we can see that we are lazy
loading dynamic imports by using promises.
Those directives tell the compiler to create a chunk for that route, and
when that route is dynamically requested by the user, the chunk is downloaded to
the browser. The issue with this code is that there is no error handling, and
promises can fail to download the chunk resulting on the ChunkLoadError.
To address this issue, we create a loader component
that can manage both the error and attempts to download the requested chunk. At
the same time, this component needs to be able to limit the number of retries, to
avoid an infinite loop, and decide to load the entire app again. Let’s look at
a simple implementation on how that could be done.
Loader Component
|
Using the Loader Component
|
After looking at our solution, we can see that we are lazy
loading the loader component which manages the promises, errors and retry activities.
The component uses a default limit for the number of attempts that should try to
download the next route. This is done by calling the same function recursively
and decreasing the limit with every attempt until the limit is decrease to zero. When the limit is reached, it does the next
best thing, which is to reload the application. If the chunk files for the current version of
the application are still available, the retry logic should be able to solve
the problem. Otherwise, a page reload takes place to download the application with
the updated chunk information.
Code Gists
Route Loader Gist Routes GistConclusion
For this simple implementation, we decided to reload the
application when a retry continues to fail. Depending on the use-case, the
approach can be different. For example, a nice message can be displayed to the
user explaining that a new update is available, and the application needs to be
updated. This is the best user experience as feedback is provided to the user.
An important concern to consider when using Code Splitting is that a ChuckLoadError can take place for users with network issues or when a new update is pushed to production. Therefore, additional design and architecture considerations must be thought of before just adding the code splitting performance improvement to a React single page application.
Thanks for reading.