This tutorial explains how to use the Performance API to record DevTool-like statistics from real users accessing your application.
Assessing web application performance using browser DevTools is useful, but it’s not easy to replicate real-world usage. People in different locations using different devices, browsers, and networks will all have differing experiences.
An Introduction to the Performance API
The Performance API uses a buffer to record DevTool-like metrics in object properties at certain points in the lifetime of your web page. Those points include:
- Page navigation: record page load redirects, connections, handshakes, DOM events, and more.
- Resource loading: record asset loading such as images, CSS, scripts, and Ajax calls.
- Paint metrics: record browser rendering information.
- Custom performance: record arbitrary application processing times to find slow functions.
if ('performance' in window) // call Performance APIs
Note: be aware that Safari doesn’t support all methods, despite implementing most of the API.
The custom (user) performance APIs are also replicated in:
- the Node.js built-in
- the Deno performance API (scripts using it must be run with the
Date() Good Enough?
You may have seen examples using the
Date() function to record elapsed times. For example:
const start = new Date(); // ... run code ... const elapsed = new Date() - start;
Date() calculations are limited to the closest millisecond and based on the system time, which can be updated by the OS at any point.
The Performance API uses a separate, higher-resolution timer that can record in fractions of a millisecond. It also offers metrics that would be impossible to record otherwise, such as redirect and DNS lookup timings.
Recording Performance Metrics
Calculating performance metrics in client-side code is useful if you can record it somewhere. You can send statistics to your server for analysis using Ajax Fetch / XMLHttpRequest requests or the Beacon API.
Alternatively, most analytic systems offer custom event-like APIs to record timings. For example, the Google Analytics User Timings API can record the time to
DOMContentLoaded by passing a category (
'pageload'), variable name (
"DOMready"), and a value:
const pageload = performance.getEntriesByType( 'navigation' ); ga('send', 'timing', 'pageload', 'DOMready', pageload.domContentLoadedEventStart);
This example uses the Page Navigation Timing API. so let’s start there …
Page Navigation Timing
Testing your site on a fast connection is unlikely to be indicative of user experience. The browser DevTools Network tab allows you to throttle speeds, but it can’t emulate poor or intermittent 3G signals.
The Navigation Timing API pushes a single
PerformanceNavigationTiming object to the performance buffer. It contains information about redirects, load times, file sizes, DOM events, and so on, observed by a real user.
Access the object by running:
const pagePerf = performance.getEntriesByType( 'navigation' );
Or access it by passing the page URL (
window.location) to the
const pagePerf = performance.getEntriesByName( window.location );
Both return an array with a single element containing an object with read-only properties. For example:
[ name: "https://site.com/", initiatorType: "navigation", entryType: "navigation", initiatorType: "navigation", type: "navigate", nextHopProtocol: "h2", startTime: 0 ... ]
The object includes resource identification properties:
|name||the resource URL|
|entryType||performance type —
|initiatorType||resource which initiated the download —
|serverTiming||array of PerformanceServerTiming objects|
duration metrics are written to the HTTP
Server-Timing header by the server response.
The object includes resource timing properties in milliseconds relative to the start of the page load. Timings would normally be expected in this order:
|startTime||timestamp when fetch started —
|workerStart||timestamp before starting the Service Worker|
|redirectStart||timestamp of the first redirect|
|redirectEnd||timestamp after receiving the last byte of the last redirect|
|fetchStart||timestamp before the resource fetch|
|domainLookupStart||timestamp before the DNS lookup|
|domainLookupEnd||timestamp after the DNS lookup|
|connectStart||timestamp before establishing a server connection|
|connectEnd||timestamp after establishing a server connection|
|secureConnectionStart||timestamp before the SSL handshake|
|requestStart||timestamp before the browser request|
|responseStart||timestamp when the browser receives the first byte of data|
|responseEnd||timestamp after receiving the last byte of data|
|duration||the time elapsed between startTime and responseEnd|
The object includes download size properties in bytes:
|transferSize||the resource size, including the header and body|
|encodedBodySize||the resource body size before decompressing|
|decodedBodySize||the resource body size after decompressing|
Finally, the object includes further navigation and DOM event properties (not available in Safari):
|redirectCount||number of redirects|
|unloadEventStart||timestamp before the
|unloadEventEnd||timestamp after the
|domInteractive||timestamp when HTML parsing and DOM construction is complete|
|domContentLoadedEventStart||timestamp before running
|domContentLoadedEventEnd||timestamp after running
|domComplete||timestamp when DOM construction and
|loadEventStart||timestamp before the page
|loadEventEnd||timestamp after the page
Example to record page loading metrics after the page has fully loaded:
'performance' in window && window.addEventListener('load', () => const pagePerf = performance.getEntriesByName( window.location ), pageDownload = pagePerf.duration, pageDomComplete = pagePerf.domComplete; );
How to Make Your Site Faster with the Performance API