React Application Performance Profiling

Today we’ll talk about measuring the performance of rendering React components using the React Profiler API. We will also evaluate interactions with the component using the new experimental Interaction Tracing API. In addition, we will use the User Timing API to take our own measurements.

We will use the React Movies Queue application as a platform for experiments.


React Movies Queue App

React Profiler API


The React Profiler API is designed to measure rendering performance and helps identify application performance bottlenecks.

import React, { Fragment, unstable_Profiler as Profiler} from "react";

The component Profileraccepts a callback onRenderas a property. It is called every time a component in the profiled tree commits an update.

const Movies = ({ movies, addToQueue }) => (
  <Fragment>
    <Profiler id="Movies" onRender={callback}>

Let’s, for test purposes, try to measure the time required to render parts of a component Movies. Here is how it looks.


The React Movies Queue app and Movies research using the React developer

callback toolsonRenderaccept parameters that describe what is being rendered and the time it takes to render. This includes the following:

  • id: Property idfrom the component tree Profilerfor which the change was committed.
  • phase: or mount(if the tree was mounted), or update(if the tree was re-rendered).
  • actualDuration: The time taken to render a fixed update.
  • baseDuration: Estimated time to render the entire subtree without caching.
  • startTime: The time React started rendering this update.
  • commitTime: the time when React committed this update.
  • interactions: Many “interactions” for this update.

const callback = (id, phase, actualTime, baseTime, startTime, commitTime) => {
    console.log(`${id}'s ${phase} phase:`);
    console.log(`Actual time: ${actualTime}`);
    console.log(`Base time: ${baseTime}`);
    console.log(`Start time: ${startTime}`);
    console.log(`Commit time: ${commitTime}`);
}

We’ll load the page and go to the Chrome Developer Tools console. There we should see the following.


Profiling results in the developer tools

We can also open the React developer tools, go to the bookmarkProfilerand visualize information about the time of component rendering. Below is a visualization of this time in the form of a flame graph.


Working with profiling results in React Developer Tools

I also like to use the view mode hereRanked, which provides an ordered view of the data. As a result, the components that take the most time to render are at the top of the list.


Viewing profiling results in Ranked mode.

In addition, you can use several components to take measurements in different parts of the applicationProfiler:

import React, { Fragment, unstable_Profiler as Profiler} from "react";

render(
  <App>
    <Profiler id="Header" onRender={callback}>
      <Header {...props} />
    </Profiler>
    <Profiler id="Movies" onRender={callback}>
      <Movies {...props} />
    </Profiler>
  </App>
);

And how to analyze user interactions with components?

Interaction Tracing API


It would be nice to be able to track user interactions with the application interface (like clicks on items). This will allow you to find answers to interesting questions like this: "How long did it take to update the DOM after clicking on this button?". Fortunately, React has experimental support for analyzing user interactions with the application using the Interaction Tracing API from the new scheduler package . You can read the documentation on it here .

Information about the interactions between the user and the application is provided with descriptions (for example, “The user clicked on the Add to Cart button”) and time stamps. In addition, when setting up analysis of interactions, callbacks are used in which actions corresponding to one or another interaction are performed.

In our application there is a button Add Movie To Queueon which the icon is displayed +. It serves to add movies to the viewing queue.


Button for adding a movie to the viewing queue

Here is an example of code that monitors status updates for this user interaction with the application:

import { unstable_Profiler as Profiler } from "react";
import { render } from "react-dom";
import { unstable_trace as trace } from "scheduler/tracing";

class MyComponent extends Component {
  addMovieButtonClick = event => {
    trace("Add To Movies Queue click", performance.now(), () => {
      this.setState({ itemAddedToQueue: true });
    });
  };
}

We can record information about this interaction and learn about its duration by contacting the React developer tools.


Analysis of user interaction with an application element

Using the Interaction Tracing API, you can also collect information about the first component rendering:

import { unstable_trace as trace } from "scheduler/tracing";

trace("initial render", performance.now(), () => {
   ReactDom.render(<App />, document.getElementById("app"));
});



Analysis of the first rendering of the component

The author of the API gives other examples of its use. For example, illustrating the profiling of asynchronous tasks.

Puppeteer


For automation of testing user interaction with application elements, the use of Puppeteer may seem interesting . This is a Node.js library that provides access to a high-level API designed to control a Chrome browser without a user interface using the DevTools protocol.

When using Puppeteer, the developer is provided with helper methods tracing.start() and tracing.stop()designed to collect DevTools performance indicators. Below is an example of using these mechanisms to collect data about what happens when you click on the button of interest to us.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  const navigationPromise = page.waitForNavigation();
  await page.goto('https://react-movies-queue.glitch.me/')
  await page.setViewport({ width: 1276, height: 689 });
  await navigationPromise;

  const addMovieToQueueBtn = 'li:nth-child(3) > .card > .card__info > div > .button';
  await page.waitForSelector(addMovieToQueueBtn);

  //  ...
  await page.tracing.start({ path: 'profile.json' });
  //   
  await page.click(addMovieToQueueBtn);
  //  
  await page.tracing.stop();

  await browser.close();
})()

Now, having loaded the file profile.jsoninto the Performancedeveloper’s toolbar, we can see what function calls were triggered by the click of a button.


Analysis of the consequences of clicking the button

If you are interested in the topic of analysis of component performance - take a look at this material.

User Timing API


The User Timing API allows the developer to create custom performance metrics using highly accurate time stamps. The method window.performance.mark()creates a timestamp to which the name is assigned. The method window.performance.measure()allows you to find out the time elapsed between two measurements.

//     
performance.mark('Movies:updateStart');
//  

//     
performance.mark('Movies:updateEnd');

//        
performance.measure('moviesRender', 'Movies:updateStart', 'Movies:updateEnd');

When profiling a React application using the PerformanceChrome Developer Tools tab , you can find a section Timingfilled with temporary metrics regarding React components. React, during rendering, can publish this information using the User Timing API.


Performance tab of the Chrome developer tools

Note that the User Timings APIs are removed from the React DEV assemblies, replacing it with the React Profiler API, which provides more accurate timestamps. Perhaps in the future support for this API will be returned by doing it for those browsers that support the User Timing Level 3 specification. 

On the Internet, you may find React sites that use the User Timing API to determine their own metrics. This includes, for example, the RedditTime to first post title visiblemetric and the Spotify metricTime to playback ready.


Special metrics used on React sites

Metrics created by the User Timing API are conveniently displayed in the Lighthouse panel of theChrome developer tools.


Metrics in the Lighthouse panel

For example, the latest versions of Next.js include custom metrics and mechanisms for measuring many different events. Including the following:

  • Next.js-hydration: time required to bring pre-rendered markup to working condition.
  • Next.js-nav-to-render: The time from the start of navigation to the start of rendering.

All these measurements are displayed in the area Timings.


Analysis of Next.js Metrics

Developer Tools and Lighthouse


I remind you that Lighthouse and the PerformanceChrome Developer Toolbar can be used to deeply analyze the loading process and the performance of React applications. Here you can find metrics that particularly affect the perception of pages by users.


Analysis of page performance

Those working with React might like the fact that they will have new metrics at their disposal - like the Total Blocking Time (TBT) metric , which gives information about how long the page is in non-interactive mode until the moment when it can reliably work in interactive mode ( Time to Interactive ). Here are the TBT indicators (“before” and “after”) for an application that employs experimental competitive mode, the use of which helps the application to adapt to the features of the environment in which it is running.


Modify TBT

These tools are useful in analyzing application performance bottlenecks, such as tasks that take a long time to complete, delaying the application’s online transition. For example, this may relate to an analysis of the application’s reaction speed to button presses.


Analysis of the

Lighthouse application , in addition, gives React developers a lot of specific tips on a lot of issues. Below is the analysis result in Lighthouse 6.0 . Here, the Remove unused JavaScript section is opened, which reports on unused JavaScript code that is loaded into the application, which can be imported usingReact.lazy().


Analyzing an application in Lighthouse

Applications are always useful to test on hardware, which is most likely available to its end users. I often rely on Webpagetest and RUM and CrUX datain such matters, which allow me to get more complete information about application performance.

Dear readers! How do you research the performance of your React applications?


All Articles