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:
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.
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.
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
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.
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 like
validate-password, it could theoretically help a hacker use a timing attack.
Bonus: Working with resource timings programmatically
- Server-Timing spec: https://w3c.github.io/server-timing/
server-timingnpm module: https://www.npmjs.com/package/server-timing
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.