Ben Edelstein Founder @LogRocket, formerly @Google

Visualizing backend performance in the Chrome DevTools

2 min read 613

Chrome’s network panel has a number of helpful visualizations for understanding network request/response performance. In this post, I’ll break down the request lifecycle waterfall, and show you how to pipe backend tracing information into the network panel.

By default, Chrome breaks down the life of a request into 8 parts:

Queueing and Stalled show the time a requests needs to wait before being acted on by the browser. There are a few reasons that a request could be delayed at this stage. Browsers will sometimes prioritize loading resources like scripts, and CSS, before loading other resources. Also, there is a maximum of 6 concurrent TCP connections allowed for HTTP 1 requests.

DNS Lookup, Initial Connection and SSL are fairly self-explanatory- showing the time spent in these respective parts of the request lifecycle.

Request sent is the amount of time it takes the browser to transmit the request to the server. This step is generally very quick, since it represents only the amount of time it takes the browser to dispatch the request.

Waiting (Time to first bite) shows the amount of time the browser needs to wait to start receiving data from the server after making an initial request. During this time, the server does whatever work is required to return the requested resource. In a typical API request, this is where the majority of latency occurs, and is usually the step that developers have the most control over optimizing. With this in mind, it can be helpful to display more granular data on what’s going on “inside” the server, which I’ll explain shortly.

Finally,Content Download is the amount of time it takes to receive the entire stream of bites from the server, after receiving the “first bite”. Latency here is mostly dependent on network connection speed, but obviously optimizing for smaller resources will reduce time in this step.

Sending backend timings

What goes on in the Waiting (TTFB) step can be a bit of a mystery, since a server could do any number of things when responding to a request. Chrome has an API for sending custom timings from the server using the Server-Timing header:

In this basic node/express server, you can see the format of the timing header, which I set for the / request. Here, I hardcoded some example values, but these would normally be filled in programatically.

Then, when I hit the / route, the timings show up in the Chrome network panel.

It is easy enough to build up the Server-Timing header manually, but there are also some nice helper libraries like server-timing (on NPM) that have a cleaner API for doing this.

Sending Server-Timing headers is useful when you visit your app and notice latency in QA, since you can understand if a particular action on the server was slow. However, this API is particularly useful if you’re using a frontend logging service like LogRocket, that records all network requests and their headers. Then, if you’re investigating a network request that hung or was extremely slow, you can see the backend timings and figure out what went wrong.

Keep in mind, however, that any timing information you expose is publicly visible so if you exposed the duration of a step likevalidate-password, it could theoretically help a hacker use a timing attack.

Bonus: Working with resource timings programmatically

Calling performance.getEntriesByType('resource') returns a list of resources and their respective timings. This is useful for analyzing timings in the JavaScript console, or to grab programmatically in your app and send to an analytics store.

Further resources


Get set up with LogRocket's modern error tracking in minutes:

  1. Visit to get an app ID
  2. Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not server-side
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    Add to your HTML:

    <script src=""></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin
Get started now
Ben Edelstein Founder @LogRocket, formerly @Google

Leave a Reply