Navigation and resource timings

Navigation timings are metrics measuring a browser's document navigation events. Resource timings are detailed network timing measurements regarding the loading of an application's resources. Both provide the same read-only properties, but navigation timing measures the main document's timings whereas the resource timing provides the times for all the assets or resources called in by that main document and the resources' requested resources.

The general performance timings below have been deprecated in favor of the Performance Entry API, which provides for marking and measuring times along the navigation and resource loading process. While deprecated, they are supported in all browsers.

Performance Timings

The performanceTiming API, a JavaScript API for measuring the loading performance of the requested page, is deprecated but supported in all browsers. It has been replaced with the performanceNavigationTiming API.

The performance timing API provided read only times, in milliseconds(ms), describing at what time each point in the page loading process was reached. As displayed in the image below, the navigation process goes from navigationStart, unloadEventStart, unloadEventEnd, redirectStart, redirectEnd, fetchStart, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, responseStart, responseEnd, domLoading, domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd, domComplete, loadEventStart, and loadEventEnd.

Navigation Timing event metrics

With the metrics above, and a bit of math, we can calculate many important metrics like time to first byte, page load time, dns lookup, and whether the connection is secure.

To help measure the time it takes to complete all the steps, the Performance Timing API provides read only measurements of navigation timings. To view and capture our app's timing we enter:

js
let time = window.performance.timing;

We can then use the results to measure how well our app is performing.

entering window.performance.timing in the console lists all the timings in the PerformanceNavigationTiming interface

The order is:

Performance Timings Details
navigationStart When the prompt for unload terminates on the previous document in the same browsing context. If there is no previous document, this value will be the same as PerformanceTiming.fetchStart.
secureConnectionStart When the secure connection handshake starts. If no such connection is requested, it returns 0.
redirectStart When the first HTTP redirect starts. If there is no redirect, or if one of the redirects is not of the same origin, the value returned is 0.
redirectEnd

When the last HTTP redirect is completed, that is when the last byte of the HTTP response has been received. If there is no redirect, or if one of the redirects is not of the same origin, the value returned is 0.

connectEnd When the connection is opened network. If the transport layer reports an error and the connection establishment is started again, the last connection establishment end time is given. If a persistent connection is used, the value will be the same as PerformanceTiming.fetchStart. A connection is considered as opened when all secure connection handshake, or SOCKS authentication, is terminated.
connectStart When the request to open a connection is sent to the network. If the transport layer reports an error and the connection establishment is started again, the last connection establishment start time is given. If a persistent connection is used, the value will be the same as PerformanceTiming.fetchStart.
domainLookupEnd When the domain lookup is finished. If a persistent connection is used, or the information is stored in a cache or a local resource, the value will be the same as PerformanceTiming.fetchStart.
domainLookupStart When the domain lookup starts. If a persistent connection is used, or the information is stored in a cache or a local resource, the value will be the same as PerformanceTiming.fetchStart.
fetchStart When the browser is ready to fetch the document using an HTTP request. This moment is before the check to any application cache.
requestStart When the browser sent the request to obtain the actual document, from the server or from a cache. If the transport layer fails after the start of the request and the connection is reopened, this property will be set to the time corresponding to the new request.
responseStart When the browser received the first byte of the response, from the server from a cache, or from a local resource.
responseEnd When the browser received the last byte of the response, or when the connection is closed if this happened first, from the server, the cache, or from a local resource.
domLoading When the parser started its work, that is when its Document.readyState changes to 'loading' and the corresponding readystatechange event is thrown.
unloadEventStart When the unload event has been thrown, indicating the time at which the previous document in the window began to unload. If there is no previous document, or if the previous document or one of the needed redirects is not of the same origin, the value returned is 0.
unloadEventEnd When the unload event handler finishes. If there is no previous document, or if the previous document, or one of the needed redirects, is not of the same origin, the value returned is 0.
domInteractive When the parser finished its work on the main document, that is when its Document.readyState changes to 'interactive' and the corresponding readystatechange event is thrown.
domContentLoadedEventStart Right before the parser sent the DOMContentLoaded event, that is right after all the scripts that need to be executed right after parsing have been executed.
domContentLoadedEventEnd Right after all the scripts that need to be executed as soon as possible, in order or not, have been executed.
domComplete When the parser finished its work on the main document, that is when its Document.readyState changes to 'complete' and the corresponding readystatechange event is thrown.
loadEventStart When the load event was sent for the current document. If this event has not yet been sent, it returns 0.
loadEventEnd When the load event handler terminated, that is when the load event is completed. If this event has not yet been sent, or is not yet completed, it returns 0.

Calculating timings

We can use these values to measure specific timings of interest:

js
const dns = time.domainLookupEnd - time.domainLookupStart;
const tcp = time.connectEnd - time.connectStart;
const tls = time.requestStart - time.secureConnectionStart;

Time to first byte

Time to First Byte is the time between the navigationStart (start of the navigation) and responseStart, (when the first byte of response data is received) available in the performanceTiming API:

js
const ttfb = time.responseStart - time.navigationStart;

Page load time

Page load time is the time between navigationStart and the start of when the load event is sent for the current document. They are only available in the performanceTiming API.

js
let pageloadtime = time.loadEventStart - time.navigationStart;

DNS lookup time

The DNS lookup time is the time between domainLookupStart and domainLookupEnd. These are both available in both the performanceTiming and performanceNavigationTiming APIs.

js
const dns = time.domainLookupEnd - time.domainLookupStart;

TCP

The time it takes for the TCP handshake is the time between the connection start and connection end:

js
const tcp = time.connectEnd - time.connectStart;

TLS negotiation

secureConnectionStart will be undefined if not available, 0 if HTTPS in not used, or a timestamp if available, and used. In other words, if a secure connection was used, secureConnectionStart will be truthy, and the time between secureConnectionStart and requestStart will greater than 0.

js
const tls = time.requestStart - time.secureConnectionStart;

Performance Entry API

The general performance timings above are deprecated but fully supported. We now have the Performance Entry API, which provides for marking and measuring times along the navigation and resource loading process. You can also create marks:

js
performance.getEntriesByType("navigation").forEach((navigation) => {
  console.dir(navigation);
});

performance.getEntriesByType("resource").forEach((resource) => {
  console.dir(resource);
});

performance.getEntriesByType("mark").forEach((mark) => {
  console.dir(mark);
});

performance.getEntriesByType("measure").forEach((measure) => {
  console.dir(measure);
});

performance.getEntriesByType("paint").forEach((paint) => {
  console.dir(paint);
});

performance.getEntriesByType("frame").forEach((frame) => {
  console.dir(frame);
});

In supporting browsers, you can use performance.getEntriesByType('paint') to query the measure for first-paint and first-contentful-paint. We use performance.getEntriesByType('navigation') and performance.getEntriesByType('resource') to query the navigation and resource timings respectively.

When a user requests a website or application, to populate the browser the user agent goes through a series of steps, including a DNS lookup, TCP handshake, and TLS negotiation, before the user agent makes the actual request and the servers return the requested assets. The browser then parses the content received, builds the DOM, CSSOM, accessibility, and render trees, eventually rendering the page. Once the user agent stops parsing the document, the user agent sets the document readiness to interactive. If there are deferred scripts needing to be parsed, it will do so, then fire the DOMContentLoaded, after which the readiness is set to complete. The Document can now handle post-load tasks, after which point the document is marked as completely loaded.

js
const navigationTimings = performance.getEntriesByType("navigation");

The performance.getEntriesByType('navigation') a returns an array of PerformanceEntry objects for the navigation type.

The results of when performance.getEntriesByType('navigation'); is entered into the console for this document

A lot can be garnered from these timing. In the above image, we see via the name property that the file being timed is this document For the rest of this explanation, we use the following variable:

js
const timing = performance.getEntriesByType("navigation")[0];

Protocol

We can check the protocol used by querying:

js
const protocol = timing.nextHopProtocol;

It returns the network protocol used to fetch the resource: in this case h2 for http/2.

Compression

To get the compression savings percentage, we divide the transferSize by the decodedBodySize, and subtract that from 100%. We see a savings of over 74%.

js
const compressionSavings = 1 - timing.transferSize / timing.decodedBodySize;

We could have used

js
const compressionSavings = 1 - timing.encodedBodySize / timing.decodedBodySize;

but using transferSize includes the overhead bytes.

For comparison, we can look at the network tab and see that we transferred 22.04KB for an uncompressed file size of 87.24KB.

View of the bytes transferred and the size via the network tab

If we do the math with these numbers, we get the same result: 1 - (22.04 / 87.24) = 0.747. The navigation timings provide us a way to programmatically check transfer sizing and bandwidth savings.

Note that this is the size for this single document alone: for this resource alone, not for all the resources combined. However, the duration, load events and DOM related timings have to do with the entire navigation, not this single asset. Client-side web applications may seem faster than this one with transfer sizes under 10000 and decoded body sizes under 30000, but that doesn't mean JavaScript, CSS, or media assets aren't adding bloat. Checking compression ratios is important but ensure to also check duration and the time between when the DOMContentLoaded event ended and when the DOM is complete, as running JavaScript on the main thread for long periods of time can lead to a non-responsive user interface.

Request time

The API doesn't provide every measurement you may desire. For example, how long did the request take? We can use measurements we do have, to get our answer.

To measure the response time, subtract the request start time from the response start time. The request start is the moment immediately before the user agent starts requesting the resource from the server, or from relevant application caches or from local resources. The response start is the time immediately after the user agent's HTTP parser receives the first byte of the response from relevant application caches, or from local resources or from the server, which happens after the request is received and processed.

js
const request = timing.responseStart - timing.requestStart;

Load event duration

By subtracting the timestamp from immediately before the load event of the current document is fired from the time when the load event of the current document is completed, you can measure the duration of the load event.

js
const load = timing.loadEventEnd - timing.loadEventStart;

DOMContentLoaded event

The DOMContentLoaded event duration is measured by subtracting the time value immediately before the user agent fires the DOMContentLoaded event from the time value immediately after the event completes. Keeping this at 50ms or faster helps ensure a responsive user interface.

js
const DOMContentLoaded =
  timing.domContentLoadedEventEnd - timing.domContentLoadedEventStart;

Duration

We are provided with the duration. The duration is the difference between the PerformanceNavigationTiming.loadEventEnd and PerformanceEntry.startTime properties.

The PerformanceNavigationTiming interface also provides information about what type of navigation you are measuring, returning navigate, reload, back_forward or prerender.

Resource timing

Whereas navigation timing is for measuring the performance of the main page, generally the HTML file via which all the other assets are requested, resource timing measures timing for individual resources, the assets called in by the main page, and any assets that those resources request. Many of the measurements are similar: there is a DNS look up, TCP handshake and the secure connection start is done once per domain.

Graphic of Resource Timing timestamps

The main thing to look at for each resource.

See also