How to Make Your Site Faster with the Performance API

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:

  1. Page navigation: record page load redirects, connections, handshakes, DOM events, and more.
  2. Resource loading: record asset loading such as images, CSS, scripts, and Ajax calls.
  3. Paint metrics: record browser rendering information.
  4. Custom performance: record arbitrary application processing times to find slow functions.

All the APIs are available in client-side JavaScript, including Web Workers. You can detect API support using:

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 performance_hook module, and
  • the Deno performance API (scripts using it must be run with the --allow-hrtime permission).

Isn’t 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;

However, 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' )[0];

ga('send', 'timing', 'pageload', 'DOMready', pageload.domContentLoadedEventStart);

This example uses the Page Navigation Timing API. so let’s start there …

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 getEntriesByName() method:

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:

property description
name the resource URL
entryType performance type — "navigation" for a page, "resource" for an asset
initiatorType resource which initiated the download — "navigation" for a page
nextHopProtocol network protocol
serverTiming array of PerformanceServerTiming objects

Note: performanceServerTiming name, description, and 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:

property description
startTime timestamp when fetch started — 0 for a page
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:

property description
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):

property description
type either "navigate", "reload", "back_forward" or "prerender"
redirectCount number of redirects
unloadEventStart timestamp before the unload event of the previous document
unloadEventEnd timestamp after the unload event of the previous document
domInteractive timestamp when HTML parsing and DOM construction is complete
domContentLoadedEventStart timestamp before running DOMContentLoaded event handlers
domContentLoadedEventEnd timestamp after running DOMContentLoaded event handlers
domComplete timestamp when DOM construction and DOMContentLoaded events have completed
loadEventStart timestamp before the page load event has fired
loadEventEnd timestamp after the page load event. All assets are downloaded

Example to record page loading metrics after the page has fully loaded:

'performance' in window && window.addEventListener('load', () => 

  const
    pagePerf        = performance.getEntriesByName( window.location )[0],
    pageDownload    = pagePerf.duration,
    pageDomComplete = pagePerf.domComplete;

);

Continue reading
How to Make Your Site Faster with the Performance API
on SitePoint.