Web performance is beyond some page load numbers

Vikash Bhardwaj
Engineered @ Publicis Sapient
8 min readMay 5, 2020

Having a faster web experience is something every brand would want for their platforms. There are many case studies that highlight the importance of a faster web experience. This is my first blog from a series where I am going to share my experiences from multiple projects w.r.t. improving the performance of a platform. It’s important that I first talk about my experience on the “What” part followed by “HOW”; so let’s start with what to measure in performance.

After decades of ongoing discussions around “what is the right way to measure performance of a web page, we seem to be converging up to a great extent. All these years, I have always found it very tough to stick with one performance metric that can assure the best results and deliver great user experience. “This is because measuring performance is about capturing the complete user journey at different stages of application, and not just about capturing a single metric like DOMContentLoaded or Onload event”.

In the context of modern applications (let’s say, React), measuring the performance with the right metrics becomes more important, because there is a common pattern of rehydrating the web page at the client side to bind JavaScript events at client side. In other words, despite the fact that there is a server side rendered view, browsers take extra time to bind events as they have to do a lot of work, i.e. downloading and executing the heavy framework and component chunks as compared to Native JS code.

In my view, it’s a great start to understanding performance measurement when we look at Addy Osmani’s blog that says:

“Loading web page is a journey where different moments take place.

There’s: Is it happening? Is it useful? And, is it usable?”

Loading web page is a journey
Image Credit — Cost of Javascript

Let’s extend the above thought process of new metrics with custom metrics to measure the rendering/interactivity time for our components. The most effective way of measuring page journey starts with component architecture thinking, where we break down a complex page into multiple components. We can measure the rendering/interactivity time of these components individually with custom timers. The reported metrics can be effectively utilized for continuous improvements.

Let’s understand with an example of a product detail page where many components may exist with different criticality levels for end users’ perspective (Criticality is usually defined by the business, and has relation to conversion metrics). For instance, in the following Product Details snapshot:

  • Highlighted logo and navigation gives some feedback to the user that the page has started rendering
  • The product image and its title coveys the most relevant and contextual information about the product details that the user is looking for
  • Similarly, “Add to Bag” and “Find in Store” CTAs enable users to proceed with further action(s) into the purchase journey
Product Detail page sample layout

Now, if we want to measure the rendering/interactivity time of each of the above stated components, then we might want to categorize these to different buckets. We can also name those buckets based on our preferences. For instance:

  • The logo and navigation can be named something like “header-content-visible
  • We can name the product image and title as “primary-content-visible
  • For CTAs, we can name the event as “primary-action-available

You will use these names, that I will explain further.

How do we start measuring these different stages?

“performance” API is the Rescue!!

performance API sample

In supported browsers while there are different API methods exposed to performance object, the 2 key API methods are “performance.mark()” and “performance.measure()”. We can use mark() and measure() to measure the above stated component’s rendering performance.

performance.mark definition on MDN “The mark() method creates a timestamp in the browser's performance entry buffer with the given name. The application defined timestamp can be retrieved by one of the Performance interface's getEntries*() methods (getEntries(), getEntriesByName() or getEntriesByType()).”

As the definition says, performance.mark creates a named timestamp on the performance entry buffer. Below is a simple snippet that shows the usage of the same:

performance.mark sample code

performance.measure definition on MDN “The measure() method creates a named timestamp in the browser's performance entry buffer between marks, the navigation start time, or the current time. When measuring between two marks, there is a start mark and end mark, respectively. The named timestamp is referred to as a measure.”

Again, below is a quick sample snippet to add a measure on performance entry buffer:

performance.measure sample code

Let’s connect our use case with the “measure and mark” API to get component’s rendering time.

But first, let’s understand how can we define our measures?

If you remember, we named our component’s rendering stages as “header-content-visible, primary-content-visible and primary-action-available”, which I believe, we can also utilize as names for our measures too. This is possible because API allows us to use our custom measure names.

This means we can fire a “measure” with code like performance.measure(“header-content-visible”, “marker-a”, “marker-b”) to add a time stamp for our component rendering.

The next important question is: where should we execute this code? If you look back and see, we wanted to find the time when the Header and Navigation got rendered on the screen. We can easily figure out that we would need some sort of callback to the component’s rendering cycle and if ReactJS is used to manage the component’s views, then “componentDidMount” can be used for the same. I am sure it wouldn’t be tough to find similar callback in other view libraries too.

So by now, you know that we have three “measures”, but how about marks? Also, we wanted to fire our measure “header-content-visible” after two separate component rendering “logo and navigation”. So only componentDidMount callback for each component may not be helpful in this use case. Let’s tie the marks together and understand how we can fire the measure using multiple marks.

If we go with our components, we can define the marks as:

  • “logo-rendered-mark” and “nav-rendered-mark” respectively for logo and navigation rendering cycle. So we can define an Array with either global scope or named scope with these both names [“logo-rendered-mark”, nav-rendered-mark]
  • Similarly, we can define the second Array with [“product-img-rendered-mark”, “product-img-rendered-mark”]
  • At the last for CTAs [“atb-btn-ready-mark”, “fis-btn-ready-mark”]. If you notice, I have changed the term “rendered” with “ready” for CTAs. This is because button being visible on page doesn’t mean it has also got the JavaScript click events attached. “Ready” gives us a more appropriate hint to fire these marks only when the JS events are attached in the browser.

Having the Array available with us, now we can make sure we reduce the Array for the component name inside their respective didMount callback. While we reduce the component name, we also need to make sure that we fire the “performance.measure” as soon as the respective array’s length becomes zero. As “performance.measure” needs two marks as parameters, we can utilize some custom mark as first parameter which we can fire first thing in the page rendering cycle, and the last component’s mark name from the Array can be used as second parameter.

Using this approach, we are able to fire a measure dependent on multiple components, and find out the exact time of components rendering/Interact-able rather than relying on some standard metrics that is not giving us the right picture of performance.

What do we do with these Measures from Performance entry buffer?

By now we have seen how we can divide our page in different regions based on their criticality, and define our measures to mark the time stamp on performance entry buffer. So the question comes: what do we do with these time stamps?

In my view, this is where it becomes powerful, because this data can be collected from Performance entry buffer in browser for every user by using performance API as performance.getEntriesByType(“measure”). We can send this data to a RUM-based tool that captures the data in some sort of time series database. Later, this data can be used for performance analysis. As it captures real user data, it would actually help us get real-time performance based on the user’s devices capability rather than synthetic reports. There are many such tools like mPulse, Dynatrace, New Relic APM that have a good integration of storing this data and provide great visual representation for our analysis.

Last, but not the least: How do we analyze our Measure and Marks locally?

While we can analyze the RUM data, it is also important that we test the Measures and Markers during the development cycle while we develop the framework around it. We can use developer toolbar in Chrome or other browsers, where the performance API is supported and utilize the “performance.getEntriesByType(“measure”)” to get all the measure entries in the console panel.

Using Google Chrome, we can also see the measure under performance tab inside Timers panel. If you run the sample code below on a page, then you will see the screenshot like below to see the measure under performance tab:

window.onload = function () {performance.mark("example-marker-a");window.setTimeout(function () {performance.mark("example-marker-b");}, 2000);window.setTimeout(function () {performance.measure("Measure a to b", "example-marker-a", "example-marker-b");}, 3000);

Developer toolbar screenshot for the above code:

performance measure sample screenshot to view in chrome browser

How about measuring user interactions?

What if your page loads very fast, but further actions are so slow that users get frustrated that forces them to switch to your competitors? In my view, it’s as critical to measure the user actions like “Add to Bag, Filter the products, etc.” as it is to measure the component’s rendering performance.

We can extend this same thought process to send the beacons for time taken by different interactions using the Performance API and then utilize the same for deep analysis based on millions of user’s data.

If you’ve reached till here, thanks for reading. I hope you enjoyed as much as I did while sharing my experiences around how we used performance API to evaluate our key component’s rendering/interactivity performance for end users.

--

--