Reporting Core Web Vitals With The Performance API<\/h1>\nGeoff Graham<\/address>\n 2024-02-27T12:00:00+00:00
\n 2024-08-30T10:05:08+00:00
\n <\/header>\n
This article is sponsored by DebugBear<\/b><\/p>\n
There\u2019s quite a buzz in the performance community with the Interaction to Next Paint (INP) metric becoming an official Core Web Vitals<\/a> (CWV) metric in a few short weeks. If you haven\u2019t heard, INP is replacing the First Input Delay (FID) metric, something you can read all about here on Smashing Magazine<\/a> as a guide to prepare for the change.<\/p>\nBut that\u2019s not what I really want to talk about. With performance at the forefront of my mind, I decided to head over to MDN for a fresh look at the Performance API<\/a>. We can use it to report the load time of elements on the page, even going so far as to report on Core Web Vitals metrics in real time. Let\u2019s look at a few ways we can use the API to report some CWV metrics.<\/p>\nBrowser Support Warning<\/h2>\n
Before we get started, a quick word about browser support. The Performance API is huge in that it contains a lot of different interfaces, properties, and methods. While the majority of it is supported by all major browsers, Chromium-based browsers are the only ones that support all of the CWV properties. The only other is Firefox, which supports the First Contentful Paint (FCP) and Largest Contentful Paint (LCP) API properties.<\/p>\n
So, we\u2019re looking at a feature of features, as it were, where some are well-established, and others are still in the experimental phase. But as far as Core Web Vitals go, we\u2019re going to want to work in Chrome for the most part as we go along.<\/p>\n
First, We Need Data Access<\/h2>\n
There are two main ways to retrieve the performance metrics we care about:<\/p>\n
\n- Using the
performance.getEntries()<\/code> method, or<\/li>\n- Using a
PerformanceObserver<\/code> instance.<\/li>\n<\/ol>\nUsing a PerformanceObserver<\/code> instance offers a few important advantages:<\/p>\n\nPerformanceObserver<\/code> observes performance metrics and dispatches them over time.<\/strong> Instead, using performance.getEntries()<\/code> will always return the entire list of entries since the performance metrics started being recorded.<\/li>\nPerformanceObserver<\/code> dispatches the metrics asynchronously,<\/strong> which means they don\u2019t have to block what the browser is doing.<\/li>\n- The
element<\/code> performance metric type doesn\u2019t work<\/strong> with the performance.getEntries()<\/code> method anyway.<\/li>\n<\/ul>\nThat all said, let\u2019s create a PerformanceObserver<\/code>:<\/p>\nconst lcpObserver = new PerformanceObserver(list => {});\n<\/code><\/pre>\nFor now, we\u2019re passing an empty callback function to the PerformanceObserver<\/code> constructor. Later on, we\u2019ll change it so that it actually does something with the observed performance metrics. For now, let\u2019s start observing:<\/p>\n\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\nThe first very important thing in that snippet is the buffered: true<\/code> property. Setting this to true<\/code> means that we not only get to observe performance metrics being dispatched after<\/em> we start observing, but we also want to get the performance metrics that were queued by the browser before<\/em> we started observing.<\/p>\nThe second very important thing to note is that we\u2019re working with the largest-contentful-paint<\/code> property. That\u2019s what\u2019s cool about the Performance API: it can be used to measure very specific things but also supports properties that are mapped directly to CWV metrics. We\u2019ll start with the LCP metric before looking at other CWV metrics.<\/p>\nReporting The Largest Contentful Paint<\/h2>\n
The largest-contentful-paint<\/code> property looks at everything on the page, identifying the biggest piece of content on the initial view and how long it takes to load. In other words, we\u2019re observing the full page load and getting stats on the largest piece of content rendered in view.<\/p>\nWe already have our Performance Observer and callback:<\/p>\n
\nconst lcpObserver = new PerformanceObserver(list => {});\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\nLet\u2019s fill in that empty callback so that it returns a list of entries once performance measurement starts:<\/p>\n
\n\/\/ The Performance Observer\nconst lcpObserver = new PerformanceObserver(list => {<\/code>\n \/\/ Returns the entire list of entries<\/code>\n const entries = list.getEntries();<\/code>\n});\n\n\/\/ Call the Observer\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\nNext, we want to know which element is pegged as the LCP. It\u2019s worth noting that the element representing the LCP is always the last<\/em> element in the ordered list of entries<\/a>. So, we can look at the list of returned entries and return the last one:<\/p>\n\n\/\/ The Performance Observer\nconst lcpObserver = new PerformanceObserver(list => {\n \/\/ Returns the entire list of entries\n const entries = list.getEntries();<\/code>\n \/\/ The element representing the LCP<\/code>\n const el = entries[entries.length - 1];<\/code>\n});\n\n\/\/ Call the Observer\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\nThe last thing is to display the results! We could create some sort of dashboard UI that consumes all the data and renders it in an aesthetically pleasing way. Let\u2019s simply log the results to the console rather than switch gears.<\/p>\n
\n\/\/ The Performance Observer\nconst lcpObserver = new PerformanceObserver(list => {\n \/\/ Returns the entire list of entries\n const entries = list.getEntries();\n \/\/ The element representing the LCP\n const el = entries[entries.length - 1];<\/code>\n \n \/\/ Log the results in the console<\/code>\n console.log(el.element);<\/code>\n});\n\n\/\/ Call the Observer\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\nThere we go!<\/p>\n
\n 2024-08-30T10:05:08+00:00
\n <\/header>\n
But that\u2019s not what I really want to talk about. With performance at the forefront of my mind, I decided to head over to MDN for a fresh look at the Performance API<\/a>. We can use it to report the load time of elements on the page, even going so far as to report on Core Web Vitals metrics in real time. Let\u2019s look at a few ways we can use the API to report some CWV metrics.<\/p>\n Before we get started, a quick word about browser support. The Performance API is huge in that it contains a lot of different interfaces, properties, and methods. While the majority of it is supported by all major browsers, Chromium-based browsers are the only ones that support all of the CWV properties. The only other is Firefox, which supports the First Contentful Paint (FCP) and Largest Contentful Paint (LCP) API properties.<\/p>\n So, we\u2019re looking at a feature of features, as it were, where some are well-established, and others are still in the experimental phase. But as far as Core Web Vitals go, we\u2019re going to want to work in Chrome for the most part as we go along.<\/p>\n There are two main ways to retrieve the performance metrics we care about:<\/p>\n Using a That all said, let\u2019s create a For now, we\u2019re passing an empty callback function to the The first very important thing in that snippet is the The second very important thing to note is that we\u2019re working with the The We already have our Performance Observer and callback:<\/p>\n Let\u2019s fill in that empty callback so that it returns a list of entries once performance measurement starts:<\/p>\n Next, we want to know which element is pegged as the LCP. It\u2019s worth noting that the element representing the LCP is always the last<\/em> element in the ordered list of entries<\/a>. So, we can look at the list of returned entries and return the last one:<\/p>\n The last thing is to display the results! We could create some sort of dashboard UI that consumes all the data and renders it in an aesthetically pleasing way. Let\u2019s simply log the results to the console rather than switch gears.<\/p>\n There we go!<\/p>\nBrowser Support Warning<\/h2>\n
First, We Need Data Access<\/h2>\n
\n
performance.getEntries()<\/code> method, or<\/li>\n
PerformanceObserver<\/code> instance.<\/li>\n<\/ol>\n
PerformanceObserver<\/code> instance offers a few important advantages:<\/p>\n
\n
PerformanceObserver<\/code> observes performance metrics and dispatches them over time.<\/strong> Instead, using
performance.getEntries()<\/code> will always return the entire list of entries since the performance metrics started being recorded.<\/li>\n
PerformanceObserver<\/code> dispatches the metrics asynchronously,<\/strong> which means they don\u2019t have to block what the browser is doing.<\/li>\n
element<\/code> performance metric type doesn\u2019t work<\/strong> with the
performance.getEntries()<\/code> method anyway.<\/li>\n<\/ul>\n
PerformanceObserver<\/code>:<\/p>\n
const lcpObserver = new PerformanceObserver(list => {});\n<\/code><\/pre>\n
PerformanceObserver<\/code> constructor. Later on, we\u2019ll change it so that it actually does something with the observed performance metrics. For now, let\u2019s start observing:<\/p>\n
lcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\n
buffered: true<\/code> property. Setting this to
true<\/code> means that we not only get to observe performance metrics being dispatched after<\/em> we start observing, but we also want to get the performance metrics that were queued by the browser before<\/em> we started observing.<\/p>\n
largest-contentful-paint<\/code> property. That\u2019s what\u2019s cool about the Performance API: it can be used to measure very specific things but also supports properties that are mapped directly to CWV metrics. We\u2019ll start with the LCP metric before looking at other CWV metrics.<\/p>\n
Reporting The Largest Contentful Paint<\/h2>\n
largest-contentful-paint<\/code> property looks at everything on the page, identifying the biggest piece of content on the initial view and how long it takes to load. In other words, we\u2019re observing the full page load and getting stats on the largest piece of content rendered in view.<\/p>\n
const lcpObserver = new PerformanceObserver(list => {});\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\n
\/\/ The Performance Observer\nconst lcpObserver = new PerformanceObserver(list => {<\/code>\n
\/\/ Returns the entire list of entries<\/code>\n
const entries = list.getEntries();<\/code>\n
});\n\n\/\/ Call the Observer\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\n
\/\/ The Performance Observer\nconst lcpObserver = new PerformanceObserver(list => {\n \/\/ Returns the entire list of entries\n const entries = list.getEntries();<\/code>\n
\/\/ The element representing the LCP<\/code>\n
const el = entries[entries.length - 1];<\/code>\n
});\n\n\/\/ Call the Observer\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\n
\/\/ The Performance Observer\nconst lcpObserver = new PerformanceObserver(list => {\n \/\/ Returns the entire list of entries\n const entries = list.getEntries();\n \/\/ The element representing the LCP\n const el = entries[entries.length - 1];<\/code>\n \n
\/\/ Log the results in the console<\/code>\n
console.log(el.element);<\/code>\n
});\n\n\/\/ Call the Observer\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\n