Visualising Front-End Performance Bottlenecks
Tags: webdev, javascript, react, tutorial
Published: 2020-07-08 13:17:29

Performance is a core part of the user experience on the web. When performance is poor, users don’t convert as often.

There are many ways to quantify web performance but the principle remains the same. First you **measure** to get a baseline, then **analyze** the results and then attempt to **fix** it. This cycle can repeat until you have a new baseline that you are happy with.

![The Measure, Analyze and Fix cycle](https://dev-to-uploads.s3.amazonaws.com/i/wqt7k8kaknffjp9gkg1l.png)

The most important thing is to measure what actually matters to users. I am going to show you how to analyze and improve slow running JavaScript code. We’ll do this with the help of Chrome Developer Tools.

I’ll be covering browser APIs that let us mark and measure our code. We will use a small demo application that we can analyze and improve as we go.

## Prerequisites
If you would like to follow along with the demo, please read through the following. Otherwise, feel free to skip this section!

You will need the following installed:
- git
- npm
- a code editor of your choice

It will also be helpful to have an understanding of JavaScript, React and a basic familiarity with Chrome DevTools

## Setting up the Demo
```bash
 git clone https://github.com/richiemccoll/visualising-front-end-performance-demo.git
 git checkout before-fixes
 npm i
 npm start
```
This should open a new browser window for you that should look something like:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vh036iu9tqs44uljpo4r.png)

## User Timings API
The first thing we need to cover is how to measure slow running code.

Browsers provide an interface on the window called **window.performance**. We can use this to retrieve performance information about the current page. For the purposes of this demo, we will focus on two methods.

### **window.performance.mark()**
Implied by the name, this API will let us insert start and end marks in slow running functions. A mark is just a timestamp with an associated name.

We can use it like this:

{% gist https://gist.github.com/richiemccoll/3d50ca1851220648200e6fd7d67edb47 %}

### **window.performance.measure()**

This API will let us create a measure between two marks (start and end). A measure is also a timestamp with an associated name.

Creating this measure is what will help us visualise functions in Developer Tools. If you forget to add this, you won’t see anything 👀.

We can use it like this:

{% gist https://gist.github.com/richiemccoll/737f0bc476866bf60dc73b86d13d581f %}

That is all we need to cover from window.performance for now but I recommend checking out the full API on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Performance).

## Analysing the UI

We are going to be running this demo in development mode. Generally speaking, it’s better to run measurements on a production build. One of the reasons for this is that libraries tend to strip out code that isn’t required for production. For example, developer warnings. This can affect measurements so it’s worth keeping in mind.

The feature we will analyze is changing the order (oldest-newest) of SpaceX launches. If you have the demo up and running, try clicking the button to change the order. Now open Chrome DevTools and switch over to the Performance tab.

This screen can look a little daunting if you are not familiar with it. [This](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference) is a great link for understanding how to use it.

Let’s change the CPU throttling option to 6x slowdown and then try clicking that button a few times.

Do you notice anything different? It feels a bit sluggish. If we hit record while clicking this button, we can see what work the browser is actually doing.

![Analyzing Button click](https://dev-to-uploads.s3.amazonaws.com/i/hhkd9hj4jezrnscnph48.png)

There is a lot going on here. After we click the button, there is a big spike in JavaScript activity on the main thread (the yellow block). After the JavaScript finishes, Style and Layout (the dark purple blocks) run. The browser then paints the update to the screen (the small green blocks).

We also see a nice real world example of performance measures. The User Timings from React. These are only available in development mode and it isn’t recommended to rely on these being there. The React Profiler is the way to go for measuring React performance, I’ll be covering this in a future post.

## Getting the baseline

The first thing we want to do is get a **baseline measurement** by marking start and end points. Let’s create the start mark in the **onClick** event handler for the button.

Open `src/components/LatestLaunches.js` and add it before calling **setOrder**.

{% gist https://gist.github.com/richiemccoll/455af2d10c579aba8706fbe949fa6687 %}

With that in place we now want to mark the end and create a measure. The first thing we have to know is when the ordering has **changed**. One way to do this would be to compare the order prop value from the previous render with the current render order value. If it is different, mark the end.

We can store the value from the previous render in a ref, with a custom hook called `usePrevious`.

{% gist https://gist.github.com/richiemccoll/b2f655c73544cb7d28566af66c960848 %}

To use this custom hook, we will need to create a new effect which will run **after** the LatestLaunches component has rendered. This means we will see from the measure how much work the browser does in total.

{% gist https://gist.github.com/richiemccoll/3e8125dbed9905853578d28307ed80e4 %}

Now switch back over to Chrome DevTools, hit record and start clicking that button again!

![First measure](https://dev-to-uploads.s3.amazonaws.com/i/tn0hocua9uh8pa01ehmm.png)

The `changingOrder-measure` is our first baseline. This is the number that we will be trying to improve. On my machine I'm seeing around **800ms**.

**Remember**: We introduced some minimal instrumentation work to get the measurements (the usePrevious custom hook) so we exclude the duration of that from the measure.

## Fix # 1 and measure

Let’s tackle the low hanging fruit first. We can prevent React from re-rendering the Card component too many times. There is an out of the box utility provided by React called `memo` that we can use.

Let’s open `src/components/Card.js` and import that.

{% gist https://gist.github.com/richiemccoll/e329c5594b6b2e2dfb9fc5d3c4f458ed %}

We can then use it by passing in the component that we want to memoize.

{% gist https://gist.github.com/richiemccoll/8d6a0d4ec0b05e02d42036afe6369f57 %}

Now let’s switch back to DevTools, take another recording and see how these changes affect our baseline.

![First fix and measure](https://dev-to-uploads.s3.amazonaws.com/i/t5rjwns8q9b7pebxiojx.png)

The new baseline is between **600–700ms**. This still isn’t great. So what other fixes can we do?

Let’s think about what’s actually happening step by step when we click the button to change the order.

1. We tell the Launch store to update it’s internal ordering state.
2. React then receives this new value as props. React runs through the reconciliation algorithm to update the order of cards.
3. The browser then has to run Style to recalculate the styles that have changed for each card.
4. As the cards have changed, the browser runs Layout to calculate the sizes and positions for each one.
5. The browser will then paint the ordering update to the screen.

The one common factor across each of these steps is the number of cards. This is where we should focus the next batch of performance fixes.

Let’s see how many Card elements we have in the DOM.

**TIP**: A quick way to do this is to open the Elements tab in DevTools. Right click the div element that contains the cards and store it as a global variable. Accessing the childElementCount property tells us that there are 96 cards in the DOM.

![Card element count](https://dev-to-uploads.s3.amazonaws.com/i/flj72mvifognse7j985v.png)

From a UI perspective there are around 5–10 Cards visible at any given time. This also means we don’t need to have 96 of them in the DOM.

## Virtualisation

There is a common rendering technique designed to mitigate this problem. This concept is known as “**List Virtualisation**” or “**windowing**”. Essentially the number of DOM elements rendered at any given time is only a small part of the list. The “window” then moves when the user scrolls, updating the content on screen as they go.

There are several libraries that provide this technique out of the box. Some examples include:

- [react-window](https://github.com/bvaughn/react-window)
- [react-virtualised](https://github.com/bvaughn/react-virtualized)
- [masonic](https://github.com/jaredLunde/masonic)

I decided to opt for `masonic` in this demo as there is minimal custom implementation required to get started.

## Fix #2 and measure

Let’s import the Masonry component in `src/components/LatestLaunches.js`.

{% gist https://gist.github.com/richiemccoll/d320d400e78ea67748776e8c2178ea97 %}

Let’s change the way that we render the list of cards.

{% gist https://gist.github.com/richiemccoll/ebb4c4b4c186e4348ea9f51eb34dfc11 %}

Time for some more recording and button clicking. Let’s switch back over to Chrome DevTools.

![Virtualisation](https://dev-to-uploads.s3.amazonaws.com/i/6rwbogq8dk9v8rh0884l.png)

Nice 🔥. Things are starting to look a bit better now that we’re reducing the amount of DOM elements. The baseline is now around **70–150ms**. Using Virtualisation has managed to cut half a second of work.

## Conclusion

There are certainly more optimizations that we could do get this baseline number even smaller, however I’ll leave that as an exercise for the reader.

The key takeaway is understanding the measure, analyze and fix cycle. For front end performance issues we can use the User Timings API to do this.

If you are interested in finding out more and would like to dive deeper into web performance, please have a read through the following links.

If you have any questions or comments, please get in touch.

## Links & Attributions

- https://developer.mozilla.org/en-US/docs/Web/API/Performance
- https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference
- https://github.com/facebook/react/pull/18417
- https://web.dev/virtualize-long-lists-react-window/
- https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html