2 Simple Things We Did to Boost Our Web Lighthouse Score from 39 to 88

Code splitting and Webpack 5 are the keys to this success

Kevin Giovanni
julotech
6 min readDec 12, 2022

--

Lighthouse score before the improvement

We have several web applications at JULO, one of which integrates as a WebView to a popular super-app that we partner with. This web app was built using Create React App (CRA) scaffolding with react-script v4 interacting with the REST API on our backend. While this framework allowed us to build and iterate rapidly for our partnership integration, it initially suffered in performance.

In this article I will explain how we were able to eventually boost the performance of this web app by simply doing two things: Code Splitting and Webpack 5 implementation. Let’s deep dive into it!

Code Splitting

Code splitting is the splitting of code into various bundles or components which can then be loaded on demand or in parallel. (Source: MDN)

Without code splitting

main chunk size 1000 KB -> downloaded in 5 secs.

With code splitting

Main chunk splits into 10 small chunks of 100 KB each. Since the browser downloads the files in parallel, the load time theoretically will be 5 secs / 10.

How to do code splitting

We utilize Webpack dynamic import syntax to do it. Furthermore, we need a code splitting library to resolve the promise returned by Webpack’s dynamic imports. There are 2 recommended options by React: React Lazy or loadable/components. If you don’t need SSR support, you can stick with React Lazy. In our case, we render the components in client side so we use React Lazy instead of loadable components.

Webpack dynamic import script

import(/* webpackChunkName: "[chunkname]" */ 'path to component')

Below are two blocks of complete code. The first block of code is the main component called HelloWorldComponent which contains the content of the page/component itself.

// Description: Hello World Component
// Filename: HelloWorld.js
// Path: HelloWorld/HelloWorld.js

import React from 'react';

const HelloWorldComponent = () => {
return (
<p>Hello World!</p>
);
};

export default HelloWorldComponent;

The second one is the lazy load version of HelloWorldComponent. When we use React Lazy to load the chunk file, we need to use React Suspense to resolve the promise returned by Webpack when we use the dynamic import syntax, as shown by the code below.

// Description: Helo World Lazy Component
// Filename: index.js
// Path: HelloWorld/index.js

import React, { Suspense, lazy } from 'react';

const HelloWorldLazy = lazy(() =>
import('./HelloWorld' /* webpackChunkName: "hello-world" */),
);

const HelloWorld = () => {
return (
<Suspense fallback={null}>
<HelloWorldLazy />
</Suspense>
);
};

export default HelloWorld;

Webpack 5

Webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser.

It’s been 2 years since Webpack released version 5 on October 10, 2020 but some developers still manage to stick with Webpack 4. Webpack 5 brought some major changes that we can utilize to improve our web performance. Does it break changes? Yes, but Webpack 5 has prepared the infrastructure to support future features so the next breaking changes will not happen in the near future and it has been confirmed by the Webpack team itself.

Benefits of Webpack 5

According to the official release of Webpack 5, this version is focusing on the following:

  • Improve build performance with Persistent Caching.
  • Improve Long Term Caching with better algorithms and defaults.
  • Improve bundle size with better Tree Shaking and Code Generation.
  • Improve compatibility with the web platform.
  • Clean up internal structures that were left in a weird state while implementing features in v4 without introducing any breaking changes.
  • Prepare for future features by introducing breaking changes now, allowing us to stay on v5 for as long as possible.

Overall, Webpack 5 brings some improvements in cache policy and bundle size. One of the biggest factors of web performance is bundle size, so technically this upgrade will bring quite significant improvements to web performance, especially when we apply optimization techniques like tree shaking and code splitting.

How to migrate

Since we are using the build from CRA, the way we upgraded to the webpack version was a bit different. We couldn’t just install the dependency directly, but since CRA react-script v5 is built with Webpack 5, we could just simply upgrade the react-script version and it automatically upgraded the Webpack version as well.

In high-level, the steps are as follows:

  1. Find react-script in your package.json
  2. Update the version to 5.0.1 or latest version
  3. Install the dependency by running npm install
  4. Start your application by running npm run start or whatever command you’ve defined to start the development server of your project
  5. Check all the loader or plugins that you use (Sass, ESlint, Webpack custom configuration, etc)
  6. Measure the difference, you can compare the lighthouse score or the bundle size

Things to keep in mind

When migrating to Webpack 5, make sure that you don’t have any deprecation warnings in the current build. Migrating from Webpack 4 to Webpack 5 is not only upgrading the Webpack version, it’s about migrating the entire plugins and loaders. So you need to make sure that the latest version of the loaders and plugins are compatible with your code.

For example, there are 3 environments in the JULO infrastructure (staging, UAT, production), which consists of 3 environment files. Each environment has their own command to build the application. Previously, we were using a library called react-app-env to manage the environment files based on the command that was already defined. Below is the command that we used to build the production environment.

react-app-env - env-file=.env.production build

Since react-app-env is now deprecated, we needed to find another library that does the same job. Finally, we found env-cmd that works exactly like react-app-env. So we use it and here is the build command right now.

env-cmd -f .env.production craco build

You may ask, “Why is there craco in it?” Based on the suggestion from the CRA community, craco is the best tool that allows us to customize Webpack configuration in react CRA. In our case migrating the Webpack is not simply upgrading the react-script version, but also making sure the libraries are not deprecated.

Should you upgrade to Webpack v5?

The answer is subjective as it can have both positive and negative points, depending on how Webpack is used in your project. Most developers who use Webpack also use a lot of plugins and loaders. They don’t end up touching Webpack very much. The thing that needs to be taken care of is the compatibility of the plugins with one another and with Webpack itself.

In short, dealing with these breaking changes can be a bit tricky but it is worth making some changes in the application’s configuration for better and faster build systems.

Results

Upgrading to Webpack 5 in our case brings significant impact to both performance and build time. Below is the summary of the performance comparison before and after Code Splitting and Webpack 5 migration. We use lighthouse score for web performance metric.

Lighthouse score comparison
Code Splitting Improvement Result
Webpack 5 Migration Improvement Result

Conclusion

At last, we have proven that two simple things that we did have boosted our web performance. From the Lighthouse score results above, from 39 to 88, that’s about 125%.

Keep improving your web performance!

Doing an improvement does not always require big efforts, sometimes it just takes a few simple steps.

References

--

--