Performance, performance, performance. You can have the best website in the world, but if it takes 2 minutes to load nobody will see it. If your website takes 2 minutes to load it probably won’t be too hard to figure out why. Optimization is trickier when you try to bring down your average load time from 1 second to 0.85 second.
There are a lot of tools which can help you understand how your application works locally. The Performance API is here to help us have a granular understanding of our web pages in the wild. You can get real data and see how your site works in different browsers, networks, parts of the world and more!
The Performance API is often described as a constellation of APIs. There are too many things to describe all of it in a single article. In this post, we’ll show the most basic features to get you started with performance monitoring.
The API is evolving and there are many new features and deprecations to come. Level 2s of all the Performance APIs coming up; some of them are partially implemented, some of them are still drafts. So you should regularly check out MDN or the W3C’s website for the most recent updates.
The most basic way to measure a program’s performance is to use performance.now()
. This will return the current time at a sub-millisecond resolution. If you want to dig into high-resolution time, I highly recommend reading the W3C’s Editor’s draft on that topic.
performance.now
only allows you to measure what’s in your JavaScript code (aka user performance). Later in this post I’ll go over an example on using performance.now
.
To access different DOM and browser events we have 3 functions:
getEntries()
returns all the available performance entries. Try running performance.getEntries()
on the current page, and you will see a big array. Initially, most of the entries will relate to all the images, scripts and other things which are loaded by the page (aka resources).const tenthEntry = performance.getEntries()[10]
// on Alligator.io it will return the following object
// { initiatorType: "script",
// nextHopProtocol: "h2",
// workerStart: 526.8099999520928,
// redirectStart: 0,
// ....
// decodedBodySize: 0,
// serverTiming: [],
// name: "https://d33wubrfki0l68.cloudfront.net/bundles/e2203d1b1c14952473222bcff4c58a8bd9fef14a.js",
// entryType: "resource",
// startTime: 315.5049999477342,
// duration: 231.48499999661
//}
// We can see this is a resource entry for a script loaded from cloudfront
getEntries()
, but will give you some possibility to filter the results.There are 6 types that you can query:
We will dive into some of these types in the next sections. Here is a simple example to get started:
const paintEntries = performance.getEntriesByType('paint')
// paint Entries[0] equals {
// name: "first-paint",
// entryType: "paint",
// startTime: 342.160000000149,
// duration: 0,
// }
// paintEntries[1] equals {
// name: "first-contentful-paint",
// entryType: "paint",
// startTime: 342.160000000149,
// duration: 0,
// }
const nativeLogoPerfEntry = performance.getEntriesByName('https://alligator.io/images/alligator-logo3.svg')[0];
// It will return performance information related to the logo's performance:
// {initiatorType: "img",
// nextHopProtocol: "",
// workerStart: 539.6649999311194,
// ........
// name: "https://alligator.io/images/alligator-logo3.svg",
// entryType: "resource",
// startTime: 539.5149999530986,
// duration: 94.24000000581145
//}
If you are looking for higher level information about the site’s performance you can also call performance.toJSON()
.
For auditing specific JavaScript functions, the most basic tool is performance.now()
which we described above.
Here’s a usage example:
const firstNow = performance.now()
// This loop is just to simulate slow calculations
for (let i = 0; i < 100000; i++){
var ii = Math.sqrt(i)
}
const secondNow = performance.now()
const howLongDidOurLoopTake = secondNow - firstNow
// on my laptop it returns howLongDidOurLoopTake == 4.08500000089407 in milliseconds
The problem with now
is that it’s a bit difficult to manage if you have many measurements. A more useful tool is mark
which creates some performance entries which you can query later-on. Then you can combine markers and create new entries using measure
.
performance.mark('beginSquareRootLoop');
// This loop is just to simulate slow calculations
for (let i = 0; i < 1000000; i++){
var ii = Math.sqrt(i);
}
performance.mark('endSquareRootLoop');
// Then anywhere in your code you can use
// We create a new entry called measureSquareRootLoop which combines our two marks
performance.measure('measureSquareRootLoop','beginSquareRootLoop', 'endSquareRootLoop');
console.log(performance.getEntriesByName('beginSquareRootLoop'));
// {detail: null,
// name: "beginSquareRootLoop",
// entryType: "mark",
// startTime: 3745.360000000801,
// duration: 0}
console.log(performance.getEntriesByName('measureSquareRootLoop'));
// {detail: null,
// name: "measureSquareRootLoop",
// entryType: "measure",
// startTime: 3745.360000000801, This is the same as beginSquareRootLoop
// duration: 9.904999984428287 shows the time it took to get from beginSquareRootLoop to endSquareRootLoop
//}
Navigation is used to get a granular understanding of the critical steps to build your web page. The safest way to access the navigation data is to do:
const navigationEntry = performance.getEntriesByType('navigation')[0]
In my browser, I get:
{
unloadEventStart: 213.41000002576038,
unloadEventEnd: 213.41000002576038,
domInteractive: 975.8100000326522,
domContentLoadedEventStart: 982.2649999987334,
domContentLoadedEventEnd: 1217.9650000180118,
domComplete: 2000.960000033956,
loadEventStart: 2001.044999982696,
loadEventEnd: 2008.6500000325032,
type: "reload",
redirectCount: 0,
initiatorType: "navigation",
nextHopProtocol: "",
workerStart: 2.5550000136718154,
redirectStart: 0,
redirectEnd: 0,
fetchStart: 2.5599999935366213,
domainLookupStart: 2.5599999935366213,
domainLookupEnd: 2.5599999935366213,
connectStart: 2.5599999935366213,
connectEnd: 2.5599999935366213,
secureConnectionStart: 0,
requestStart: 2.5599999935366213,
responseStart: 107.46500000823289,
responseEnd: 214.3950000172481,
transferSize: 0,
encodedBodySize: 0,
decodedBodySize: 0,
serverTiming: [],
name: "https://alligator.io/",
entryType: "navigation",
startTime: 0,
duration: 2008.6500000325032
}
We will dive in some more detailed explanations about how to use that data in a future post. But in the meantime here is a visualization of the navigation timeline:
Anytime a resource is loaded by a page we can find its trace in the Performance Entries. All we have to do to get them is run performance.getEntriesByType('resource')
. This includes images, scripts, CSS files and more. So for example if we want to focus on the performance of images on the site we can run:
performance.getEntriesByType('resource').filter(resource=> resource.initiatorType == 'img')
Here’s one of the resources found on Alligator.io:
{
initiatorType: "img",
nextHopProtocol: "h2",
workerStart: 551.2149999849498,
redirectStart: 0,
redirectEnd: 0,
fetchStart: 551.3149999896996,
domainLookupStart: 0,
domainLookupEnd: 0,
connectStart: 0,
connectEnd: 0,
secureConnectionStart: 0,
requestStart: 0,
responseStart: 0,
responseEnd: 560.1850000093691,
transferSize: 0,
encodedBodySize: 0,
decodedBodySize: 0,
serverTiming: [],
name: "https://d33wubrfki0l68.cloudfront.net/39d2d2905588dad289b228deb021d51449f6143d/a3baf/images/logos/gatsby-logo.svg",
entryType: "resource",
startTime: 222.0450000022538,
duration: 338.1400000071153
}
This entry has a lot of 0 values as you can see, that is because we are restricted by CORS (this is a big limit of the resource timing API). So the following properties will always return 0: redirectStart, redirectEnd, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, and responseStart.
The paint API relates to events that draw pixels on the window. As we saw in a previous snippet, we have access to First Time to Paint and First Contentful Paint. If you’ve worked with front-end optimization tools like Lighthouse, you might be familiar with these terms. The first time to paint is when the first pixel shows up on the user screen. The first contentful paint is when an element defined in the DOM is first rendered. To optimize the first contentful paint you can reduce render-blocking scripts and stylesheets, use HTTP caching, optimize JavaScript boot-up and more!
These are useful metrics but are pretty limited if you are trying to understand what your users see. In order to have a good idea of your user’s performance perception, we need to combine multiple metrics.
The performance API is gigantic and is rapidly changing. The best place to look for updates is of course Alligator.io, but if you want to explore this topic really in depth you should check out the Web Performance Working Group’s page where you can find the latest working drafts and recommendations.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.