How Developers Measure and Optimize Web Performance

This post is meant to sensitize us on the importance of the performance of our web apps. If you are like me, when you started in software development, your initial concern was getting your code to run and for the application to go live. Later, you start to grapple with delivering the best user experience to users. This then leads you to start thinking of performance.

In this article, I attempt to share how to measure the performance of our application using an open-source tool known as Lighthouse. There are other tools in the market but this one is easily accessible as it ships with the Chrome browser. I will also share some practices that can help you improve the performance of your app. I will also attempt to explain some technical concepts that will be useful to us as developers.

What exactly is web performance?

The honest truth is that web performance is both subjective and objective. As a user, it is subjective. From a user’s point of view, web performance is about how fast a user perceives the site to be. That is, the user’s perception of whether the time it takes between the time s/he requests the content and the time until s/he feels the content requested is available and usable feels slow or fast.

Objectively, it is the measurable time, in milliseconds, it takes for the web page or web application to be downloaded, painted in the user’s web browser, and become responsive and interactive. That is, the objective time of when a request for content is made until the requested content is displayed on the user’s browser.

What is web performance optimization?

Web performance optimization is simply all the steps you will take as a developer to increase the subjective and objective web performance.

Why are we discussing performance and optimization?

It is important to discuss performance and optimization because of how fast or slow our app impacts our end users. And as developers, we are solving problems for our users. If we are not able to satisfy our users, we will lose money and product would suffer.

It is also important to factor in performance during development because our app will be used under different network conditions and on different devices with different capabilities.

Take a look at this 2017 study showing how long it took different mobile phones to parse 1MB of Javascript:

[caption id="attachment_66347" align="aligncenter" width="700"]

graph chart

The cost of parsing 1MB of Javascript by various mobile devices.[/caption]

This data shows us that there is a 2–5x difference in time to parse/compile code between the fastest phones on the market and average phones. This implies that while you develop with fast 4G on your MacBook or DellInspiro, your user might use your app on a 3G network with a device with a slow GPU. How do you accommodate that? That’s why you need to factor in performance while developing your apps.

How do we measure performance?

In order to develop with performance in mind, we need to be able to measure performance. How do we do that?

We can use tools such LightHouse, GTMetrix.comWebPageTestPageSpeedInsights. In this article, I will be using Lighthouse.

Measuring Performance

To show you how lighthouse works, I will demo it with a personal project. This app is live here: 

Follow the following steps:

1. Launch a new Chrome tab in Incognito mode (Control+Shift+N). This is so that your chrome extensions (if you have any) do not interfere with the audits.

2. Type  (or any app whose performance you want to measure) into the address bar and open up the Chrome developer tools using either of these commands: Command+Option+I (Mac) or Control+Shift+I (Windows, Linux).

3. You can open the developer tools section to the right as shown in the image below for convenience.

4. Switch over to the Audits tab. The Audits tab uses LightHouse. So with this, we will measure the performance of the app. Lighthouse will also provide insights on how we can improve performance.

[caption id="attachment_66348" align="aligncenter" width="700"]

page performance audit

Image showing the audits tab in Chrome Dev Tools[/caption]

For this audit, you can configure Lighthouse however you wish. In this instance, we want to audit the site for performance on a mobile device. We also want to simulate a slow 4G network and 4x CPU slowdown( That is, your current device performance slowed down by a factor of 4).

To run the audit, click on the Run audits button.

Below is the result of running the audits:

[caption id="attachment_66349" align="aligncenter" width="700"]

page performance audit

The result of the audit on https://sammie-fast-food-fast-react.herokuapp.com[/caption]

This audit scores the performance of the app at 57%. However, there are important metrics that Lighthouse exposes to us.

[caption id="attachment_66350" align="aligncenter" width="700"]

web performance metrics

Important metrics of web performance[/caption]

These metrics shown above are used to specifically measure how performant our app is. You can click on the learn more link on each to read more on each metric(I would not want to do that in this article so this doesn’t get too long).

By using the Audits Tab, we can know how performant our app is.

So how do we optimize the performance?

We can optimize the performance of our app at load time or runtime. In this article, I will be looking at load time.

As a developer, what I can do now to improve performance?

Write mobile-first CSS

  • This will give the browsers of smaller devices less work to do as the browser only needs to the calculations for their screen sizes.

Code Splitting.

  • Today, most web applications are built using frameworks/libraries such as React, Vue, Angular, etc. These libraries use tools such as Webpack or Browserify to bundle your application code. Bundling is simply the process of following imported files and merging them into a single file: a “bundle”. The idea of code splitting is to serve only the code needed for the routes or components or user actions that a user requests for per time(lazy loading) instead of serving all the application code at the same time. (eager loading).
  • How do you implement code splitting?/Code Splitting in details

Code splitting is done using dynamic imports using the dynamic import() syntax. Note that the import() syntax is an ECMAScript (JavaScript) proposal not currently part of the language standard. It is expected to be accepted in the near future.

Here is a simple example of the use of the dynamic imports syntax taken from the React documentation:

Before:

[pastacode lang="javascript" manual="import%20%7Badd%7D%20from%20%E2%80%98.%2Fmath%E2%80%99console.log(add(2%2C2))%0AWith%20import()%20syntax%2C%20this%20becomes%3A%0A%0Aimport(%22.%2Fmath%22).then(math%20%3D%3E%20%7B%0A%20%20console.log(math.add(2%2C%202))%3B%0A%7D)%3B" message="" highlight="" provider="manual"/]

Note that import() calls use promises internally. So, if you use import() with older browsers, remember to shim Promise using a polyfill such as es6-promise or promise-polyfill.

How then do you introduce code splitting into your app?

You can split the code for your app based on the routes. That is, you want to load the code for a route only when it is needed.

Below is a route based code splitting implementation from the React documentation using libraries like React Router with the React.lazy function.

[pastacode lang="javascript" manual="import%20%7B%20BrowserRouter%20as%20Router%2C%20Route%2C%20Switch%20%7D%20from%20'react-router-dom'%3B%0Aimport%20React%2C%20%7B%20Suspense%2C%20lazy%20%7D%20from%20'react'%3B%0A%0Aconst%20Home%20%3D%20lazy(()%20%3D%3E%20import('.%2Froutes%2FHome'))%3B%0Aconst%20About%20%3D%20lazy(()%20%3D%3E%20import('.%2Froutes%2FAbout'))%3B%0A%0Aconst%20App%20%3D%20()%20%3D%3E%20(%0A%20%20%3CRouter%3E%0A%20%20%20%20%3CSuspense%20fallback%3D%7B%3Cdiv%3ELoading...%3C%2Fdiv%3E%7D%3E%0A%20%20%20%20%20%20%3CSwitch%3E%0A%20%20%20%20%20%20%20%20%3CRoute%20exact%20path%3D%22%2F%22%20component%3D%7BHome%7D%2F%3E%0A%20%20%20%20%20%20%20%20%3CRoute%20path%3D%22%2Fabout%22%20component%3D%7BAbout%7D%2F%3E%0A%20%20%20%20%20%20%3C%2FSwitch%3E%0A%20%20%20%20%3C%2FSuspense%3E%0A%20%20%3C%2FRouter%3E%0A)%3B" message="" highlight="" provider="manual"/]

You can also implement something similar in Vue.js using the Vue-router. Below is an example of Vue-Router code implementing code splitting on the basis of the routes

[pastacode lang="javascript" manual="%7Bpath%3A%20'%2Fpublications'%2Cname%3A%20'AllPublications'%2Ccomponent%3A%20()%20%3D%3E%20import('%40%2Fpages%2FPublications%2FAllPublications')%2Cmeta%3A%20%7B%20isAuth%3A%20true%20%20%7D%7D%2C" message="" highlight="" provider="manual"/]

So, what the above means, is that the code the publications page will only be loaded when the user navigates to the route, ‘/publications’. Until the user navigates to this route, the code for the publications page is not loaded onto the browser.

Something similar is done in Angular’s routing as well. Below is an example from the Angular documentation:

[pastacode lang="javascript" manual="const%20routes%3A%20Routes%20%3D%20%5B%0A%7B%0A%20%20path%3A%20%E2%80%98customers%E2%80%99%2C%0AloadChildren%3A%20()%20%3D%3E%20%20import(%E2%80%98.%2Fcustomers%2Fcustomers.module%E2%80%99).then(mod%20%3D%3E%20mod.CustomersModule)%0A%7D%2C%0A%7B%0A%20path%3A%20%E2%80%98orders%E2%80%99%2C%0AloadChildren%3A%20()%20%3D%3E%20import(%E2%80%98.%2Forders%2Forders.module%E2%80%99).then(mod%20%3D%3E%20mod.OrdersModule)%0A%7D%2C%0A%7B%0A%20path%3A%20%E2%80%98%E2%80%99%2C%0A%20redirectTo%3A%20%E2%80%98%E2%80%99%2C%0A%20pathMatch%3A%20%E2%80%98full%E2%80%99%0A%7D%5D%3B" message="" highlight="" provider="manual"/]

In summary, when you code split based on routes, you serve only the chunks your users need per route.

See more on code splitting/Lazy loading in React here.

See more on code splitting/Lazy loading in Vue here.

See more on code splitting/Lazy loading in Angular here.

That’s quite a lot to take in.

So, now that we have looked at some code, what factors do we know that can affect the performance of our apps.

  • Network Conditions
  • Device GPU of users.

Conclusion

The performance of our apps would always have a subjective feel to it. This is because it is an experience that no one metric can fully capture. There are multiple moments during the load experience that can affect whether a user perceives it as “fast” or “slow.”

In testing, we must pay attention to the little wins that cumulatively increase application speed and result in a better experience for the end-user.

Related posts

The latest articles from Andela.

Visit our blog

Customer-obsessed? 4 Steps to improve your culture

If you get your team's culture right, you can create processes that will allow you to operationalize useful new technologies. Check out our 4 steps to transform your company culture.

How to Build a RAG-Powered LLM Chat App with ChromaDB and Python

Harness the power of retrieval augmented generation (RAG) and large language models (LLMs) to create a generative AI app. Andela community member Oladimeji Sowole explains how.

Navigating the future of work with generative AI and stellar UX design

In this Writer's Room blog, Carlos Tay discusses why ethical AI and user-centric design are essential in shaping a future where technology amplifies human potential.

We have a 96%+
talent match success rate.

The Andela Talent Operating Platform provides transparency to talent profiles and assessment before hiring. AI-driven algorithms match the right talent for the job.