How to Make Your Site Faster with the Performance API

Share this article

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:

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;

});

Page Resource Timing

The Resource Timing API pushes a PerformanceResourceTiming object to the performance buffer whenever an asset such as an image, font, CSS file, JavaScript file, or any other item is loaded by the page. Run:

const resPerf = performance.getEntriesByType( 'resource' );

This returns an array of resource timing objects. These have the same properties as the page timing shown above, but without the navigation and DOM event information.

Here’s an example result:

[
  {
    name: "https://site.com/style.css",
    entryType: "resource",
    initiatorType: "link",
    fetchStart: 150,
    duration: 300
    ...
  },
  {
    name: "https://site.com/script.js",
    entryType: "resource",
    initiatorType: "script",
    fetchStart: 302,
    duration: 112
    ...
  },
  ...
]

A single resource can be examined by passing its URL to the .getEntriesByName() method:

const resourceTime = performance.getEntriesByName('https://site.com/style.css');

This returns an array with a single element:

[
  {
    name: "https://site.com/style.css",
    entryType: "resource",
    initiatorType: "link",
    fetchStart: 150,
    duration: 300
    ...
  }
]

You could use the API to report the load time and decompressed size of each CSS file:

// array of CSS files, load times, and file sizes
const css = performance.getEntriesByType('resource')
  .filter( r => r.initiatorType === 'link' && r.name.includes('.css'))
  .map( r => ({

      name: r.name,
      load: r.duration + 'ms',
      size: r.decodedBodySize + ' bytes'

  }) );

The css array now contains an object for each CSS file. For example:

[
  {
    name: "https://site.com/main.css",
    load: "155ms",
    size: "14304 bytes"
  },
  {
    name: "https://site.com/grid.css",
    load: "203ms",
    size: "5696 bytes"
  }
]

Note: a load and size of zero indicates the asset was already cached.

At least 150 resource metric objects will be recorded to the performance buffer. You can define a specific number with the .setResourceTimingBufferSize(N) method. For example:

// record 500 resources
performance.setResourceTimingBufferSize(500);

Existing metrics can be cleared with the .clearResourceTimings() method.

Browser Paint Timing

First Contentful Paint (FCP) measures how long it takes to render content after the user navigates to your page. The Performance section of Chrome’s DevTool Lighthouse panel shows the metric. Google considers FCP times of less than two seconds to be good and your page will appear faster than 75% of the Web.

The Paint Timing API pushes two records two PerformancePaintTiming objects to the performance buffer when:

  • first-paint occurs: the browser paints the first pixel, and
  • first-contentful-paint occurs: the browser paints the first item of DOM content

Both objects are returned in an array when running:

const paintPerf = performance.getEntriesByType( 'paint' );

Example result:

[
  {
    "name": "first-paint",
    "entryType": "paint",
    "startTime": 125
  },
  {
    "name": "first-contentful-paint",
    "entryType": "paint",
    "startTime": 127
  }
]

The startTime is relative to the initial page load.

User Timing

The Performance API can be used to time your own application functions. All user timing methods are available in client-side JavaScript, Web Workers, Deno, and Node.js.

Note that Node.js scripts must load the Performance hooks (perf_hooks) module.

CommonJS require syntax:

const { performance } = require('perf_hooks');

Or ES module import syntax:

import { performance } from 'perf_hooks';

The easiest option is performance.now(), which returns a high-resolution timestamp from the beginning of the process’s lifetime.

You can use performance.now() for simple timers. For example:

const start = performance.now();

// ... run code ...

const elapsed = performance.now() - start;

Note: a non-standard timeOrigin property returns a timestamp in Unix time. It can be used in Node.js and browser JavaScript, but not in IE and Safari.

performance.now() quickly becomes impractical when managing multiple timers. The .mark() method adds a named PerformanceMark object object to the performance buffer. For example:

performance.mark('script:start');

performance.mark('p1:start');
// ... run process 1 ...
performance.mark('p1:end');

performance.mark('p2:start');
// ... run process 2 ...
performance.mark('p2:end');

performance.mark('script:end');

The following code returns an array of mark objects:

const marks = performance.getEntriesByType( 'mark' );

with entryType, name, and startTime properties:

[
  {
    entryType: "mark",
    name: "script:start",
    startTime: 100
  },
  {
    entryType: "mark",
    name: "p1:start",
    startTime: 200
  },
  {
    entryType: "mark",
    name: "p1:end",
    startTime: 300
  },
  ...
]

The elapsed time between two marks can be calculated using the .measure() method. It’s passed a measure name, the start mark name (or null to use zero), and the end mark name (or null to use the current time):

performance.measure('p1', 'p1:start', 'p1:end');
performance.measure('script', null, 'script:end');

Each call pushes a PerformanceMeasure object with a calculated duration to the performance buffer. An array of measures can be accessed by running:

const measures = performance.getEntriesByType( 'measure' );

Example:

[
  {
    entryType: "measure",
    name: "p1",
    startTime: 200,
    duration: 100
  },
  {

    entryType: "measure",
    name: "script",
    startTime: 0,
    duration: 500
  }
]

Mark or measure objects can be retrieved by name using the .getEntriesByName() method:

performance.getEntriesByName( 'p1' );

Other methods:

A PerformanceObserver can watch for changes to the buffer and run a function when specific objects appear. An observer function is defined with two parameters:

  1. list: the observer entries
  2. observer (optional): the observer object
function performanceHandler(list, observer) {

  list.getEntries().forEach(entry => {

    console.log(`name    : ${ entry.name }`);
    console.log(`type    : ${ entry.type }`);
    console.log(`duration: ${ entry.duration }`);

    // other code, e.g.
    // send data via an Ajax request

  });

}

This function is passed to a new PerformanceObserver object. The .observe() method then sets observable entryTypes (generally "mark", "measure", and/or "resource"):

let observer = new PerformanceObserver( performanceHandler );
observer.observe( { entryTypes: [ 'mark', 'measure' ] } );

The performanceHandler() function will run whenever a new mark or measure object is pushed to the performance buffer.

Self-profiling API

The Self-profiling API is related to the Performance API and can help find inefficient or unnecessary background functions without having to manually set marks and measures.

Example code:

// new profiler, 10ms sample rate
const profile = await performance.profile({ sampleInterval: 10 });

// ... run code ...

// stop profiler, get trace
const trace = await profile.stop();

The trace returns data about what script, function, and line number was executing at every sampled interval. Repeated references to the same code could indicate that further optimization may be possible.

The API is currently under development (see Chrome Status) and subject to change.

Tuning Application Performance

The Performance API offers a way to measure website and application speed on actual devices used by real people in different locations on a range of connections. It makes it easy to collate DevTool-like metrics for everyone and identify potential bottlenecks.

Solving those performance problems is another matter, but the SitePoint Jump Start Web Performance book will help. It provides a range of quick snacks, simple recipes, and life-changing diets to make your site faster and more responsive.

Frequently Asked Questions (FAQs) about Performance API

What is the Performance API and why is it important?

The Performance API is a powerful tool that allows developers to measure the performance of their websites and applications. It provides data about the time it takes for various parts of a web page or application to load and execute. This information is crucial for optimizing performance and improving user experience. By identifying bottlenecks and areas of slow performance, developers can make necessary adjustments to enhance speed and efficiency.

How does the Performance API work?

The Performance API works by providing a series of interfaces and methods that developers can use to collect performance data. These include the Performance interface, which provides access to performance-related information, and the PerformanceEntry interface, which represents a single performance metric. The API also includes methods for starting and stopping the measurement of performance metrics, and for retrieving the collected data.

What are some common uses of the Performance API?

The Performance API is commonly used to measure the load time of web pages and applications, the time it takes for scripts to execute, and the time it takes for resources to be fetched and loaded. It can also be used to measure the performance of specific parts of a web page or application, such as the rendering of images or the execution of JavaScript functions.

How can I start using the Performance API?

To start using the Performance API, you first need to access the Performance interface, which is typically available through the window.performance property. From there, you can use the various methods provided by the API to start and stop the measurement of performance metrics, and to retrieve the collected data.

What are the key differences between the Performance API and other performance measurement tools?

The Performance API provides a more detailed and accurate measurement of performance metrics than traditional tools such as timers or counters. It also allows for the measurement of performance metrics at a granular level, such as the time it takes for a specific part of a web page or application to load or execute.

Can the Performance API be used with other APIs?

Yes, the Performance API can be used in conjunction with other APIs to provide a more comprehensive view of performance. For example, it can be used with the Navigation Timing API to measure the time it takes for a web page to load, or with the Resource Timing API to measure the time it takes for resources to be fetched and loaded.

What are some limitations of the Performance API?

While the Performance API provides a powerful tool for measuring performance, it does have some limitations. For example, it cannot measure the performance of server-side operations, and it may not provide accurate measurements for operations that are performed in parallel.

How can I interpret the data provided by the Performance API?

The data provided by the Performance API can be interpreted in a number of ways, depending on the specific performance metrics you are interested in. For example, a high load time may indicate a bottleneck in your web page or application, while a high execution time may indicate a problem with your scripts.

Can the Performance API impact the performance of my website or application?

The Performance API is designed to have a minimal impact on the performance of your website or application. However, like any API, it does consume some resources, so it’s important to use it judiciously and to monitor its impact on performance.

What are some best practices for using the Performance API?

Some best practices for using the Performance API include starting and stopping the measurement of performance metrics at the appropriate times, using the API in conjunction with other APIs to provide a comprehensive view of performance, and interpreting the data provided by the API in the context of your specific performance goals.

Craig BucklerCraig Buckler
View Author

Craig is a freelance UK web consultant who built his first page for IE2.0 in 1995. Since that time he's been advocating standards, accessibility, and best-practice HTML5 techniques. He's created enterprise specifications, websites and online applications for companies and organisations including the UK Parliament, the European Parliament, the Department of Energy & Climate Change, Microsoft, and more. He's written more than 1,000 articles for SitePoint and you can find him @craigbuckler.

app performancedevtoolsperformanceperformance apiwebsite performance
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week