{"id":503,"date":"2024-02-27T11:00:00","date_gmt":"2024-02-27T12:00:00","guid":{"rendered":"https:\/\/upprofits.net\/?p=503"},"modified":"2024-08-30T11:28:33","modified_gmt":"2024-08-30T11:28:33","slug":"reporting-core-web-vitals-with-the-performance-api","status":"publish","type":"post","link":"https:\/\/upprofits.net\/index.php\/2024\/02\/27\/reporting-core-web-vitals-with-the-performance-api\/","title":{"rendered":"Reporting Core Web Vitals With The Performance API"},"content":{"rendered":"

Reporting Core Web Vitals With The Performance API<\/title><\/p>\n<article>\n<header>\n<h1>Reporting Core Web Vitals With The Performance API<\/h1>\n<address>Geoff Graham<\/address>\n<p> 2024-02-27T12:00:00+00:00<br \/>\n 2024-08-30T10:05:08+00:00<br \/>\n <\/header>\n<p>This article is sponsored by <b>DebugBear<\/b><\/p>\n<p>There\u2019s quite a buzz in the performance community with the Interaction to Next Paint (INP) metric becoming an official <a href=\"https:\/\/www.debugbear.com\/docs\/metrics\/core-web-vitals?utm_campaign=sm-4\">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 <a href=\"https:\/\/www.smashingmagazine.com\/2023\/12\/preparing-interaction-next-paint-web-core-vital\/\">you can read all about here on Smashing Magazine<\/a> as a guide to prepare for the change.<\/p>\n<p>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 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Performance_API\">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<h2 id=\"browser-support-warning\">Browser Support Warning<\/h2>\n<p>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<p>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<h2 id=\"first-we-need-data-access\">First, We Need Data Access<\/h2>\n<p>There are two main ways to retrieve the performance metrics we care about:<\/p>\n<ol>\n<li>Using the <code>performance.getEntries()<\/code> method, or<\/li>\n<li>Using a <code>PerformanceObserver<\/code> instance.<\/li>\n<\/ol>\n<p>Using a <code>PerformanceObserver<\/code> instance offers a few important advantages:<\/p>\n<ul>\n<li><strong><code>PerformanceObserver<\/code> observes performance metrics and dispatches them over time.<\/strong> Instead, using <code>performance.getEntries()<\/code> will always return the entire list of entries since the performance metrics started being recorded.<\/li>\n<li><strong><code>PerformanceObserver<\/code> dispatches the metrics asynchronously,<\/strong> which means they don\u2019t have to block what the browser is doing.<\/li>\n<li><strong>The <code>element<\/code> performance metric type doesn\u2019t work<\/strong> with the <code>performance.getEntries()<\/code> method anyway.<\/li>\n<\/ul>\n<p>That all said, let\u2019s create a <code>PerformanceObserver<\/code>:<\/p>\n<pre><code class=\"language-javascript\">const lcpObserver = new PerformanceObserver(list => {});\n<\/code><\/pre>\n<p>For now, we\u2019re passing an empty callback function to the <code>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<div class=\"break-out\">\n<pre><code class=\"language-javascript\">lcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\n<p>The first very important thing in that snippet is the <code>buffered: true<\/code> property. Setting this to <code>true<\/code> means that we not only get to observe performance metrics being dispatched <em>after<\/em> we start observing, but we also want to get the performance metrics that were queued by the browser <em>before<\/em> we started observing.<\/p>\n<p>The second very important thing to note is that we\u2019re working with the <code>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<h2 id=\"reporting-the-largest-contentful-paint\">Reporting The Largest Contentful Paint<\/h2>\n<p>The <code>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<p>We already have our Performance Observer and callback:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">const lcpObserver = new PerformanceObserver(list => {});\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\n<p>Let\u2019s fill in that empty callback so that it returns a list of entries once performance measurement starts:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">\/\/ The Performance Observer\nconst lcpObserver = new PerformanceObserver(list => {<\/code>\n <code style=\"font-weight: bold\">\/\/ Returns the entire list of entries<\/code>\n <code style=\"font-weight: bold\">const entries = list.getEntries();<\/code>\n<code class=\"language-javascript\">});\n\n\/\/ Call the Observer\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\n<p>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 <em>last<\/em> element in the <a href=\"https:\/\/w3c.github.io\/largest-contentful-paint\/#sec-report-largest-contentful-paint\">ordered list of entries<\/a>. So, we can look at the list of returned entries and return the last one:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">\/\/ The Performance Observer\nconst lcpObserver = new PerformanceObserver(list => {\n \/\/ Returns the entire list of entries\n const entries = list.getEntries();<\/code>\n <code style=\"font-weight: bold\">\/\/ The element representing the LCP<\/code>\n <code style=\"font-weight: bold\">const el = entries[entries.length - 1];<\/code>\n<code class=\"language-javascript\">});\n\n\/\/ Call the Observer\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\n<p>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<div class=\"break-out\">\n<pre><code class=\"language-javascript\">\/\/ 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 <code style=\"font-weight: bold\">\/\/ Log the results in the console<\/code>\n <code style=\"font-weight: bold\">console.log(el.element);<\/code>\n<code class=\"language-javascript\">});\n\n\/\/ Call the Observer\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n<\/code><\/pre>\n<\/div>\n<p>There we go!<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/1-smashingmagazine-devtools-console.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"505\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/1-smashingmagazine-devtools-console.png\" alt=\"Open Chrome window showing the LCP results in the DevTools console while highlighting the result on the Smashing Magazine homepage.\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n LCP support is limited to Chrome and Firefox at the time of writing. (<a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/1-smashingmagazine-devtools-console.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>It\u2019s certainly nice knowing which element is the largest. But I\u2019d like to know more about it, say, how long it took for the LCP to render:<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">\/\/ The Performance Observer\nconst lcpObserver = new PerformanceObserver(list => {\n\n const entries = list.getEntries();\n const lcp = entries[entries.length - 1];\n\n entries.forEach(entry => {\n \/\/ Log the results in the console\n console.log(\n `The LCP is:`,\n lcp.element,\n `The time to render was ${entry.startTime} milliseconds.`,\n );\n });\n});\n\n\/\/ Call the Observer\nlcpObserver.observe({ type: \"largest-contentful-paint\", buffered: true });\n\n\/\/ The LCP is:\n\/\/ <h2 class=\"author-post__title mt-5 text-5xl\">\u2026<\/h2>\n\/\/ The time to render was 832.6999999880791 milliseconds.\n<\/code><\/pre>\n<\/div>\n<h2 id=\"reporting-first-contentful-paint\">Reporting First Contentful Paint<\/h2>\n<p>This is all about the time it takes for the very first piece of DOM to get painted on the screen. Faster is better, of course, but the way Lighthouse reports it, a \u201cpassing\u201d score comes in between 0 and 1.8 seconds.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/www.debugbear.com\/docs\/web-performance-metrics#first-contentful-paint-fcp?utm_campaign=sm-4\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"411\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/2-timeline-mobile-screen-frames.png\" alt=\"Showing a timeline of mobile screen frames measured in seconds and how much is painted to the screen at various intervals.\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n Image source: <a href=\"https:\/\/www.debugbear.com\/docs\/web-performance-metrics#first-contentful-paint-fcp?utm_campaign=sm-4\">Source: DebugBear<\/a>. (<a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/2-timeline-mobile-screen-frames.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>Just like we set the <code>type<\/code> property to <code>largest-contentful-paint<\/code> to fetch performance data in the last section, we\u2019re going to set a different type this time around: <code>paint<\/code>.<\/p>\n<p>When we call <code>paint,<\/code> we tap into the <code>PerformancePaintTiming<\/code> interface that opens up reporting on <strong>first paint<\/strong> and <strong>first contentful paint<\/strong>.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">\/\/ The Performance Observer\nconst paintObserver = new PerformanceObserver(list => {\n const entries = list.getEntries();\n entries.forEach(entry => { \n \/\/ Log the results in the console.\n console.log(\n `The time to ${entry.name} took ${entry.startTime} milliseconds.`,\n );\n });\n});\n\n\/\/ Call the Observer.\npaintObserver.observe({ type: \"paint\", buffered: true });\n\n\/\/ The time to first-paint took 509.29999999981374 milliseconds.\n\/\/ The time to first-contentful-paint took 509.29999999981374 milliseconds.\n<\/code><\/pre>\n<\/div>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/3-devtools-smashingmagazine.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"505\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/3-devtools-smashingmagazine.png\" alt=\"DevTools open on the Smashing Magazine website displaying the paint results in the console.\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/3-devtools-smashingmagazine.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>Notice how <code>paint<\/code> spits out two results: one for the <code>first-paint<\/code> and the other for the <code>first-contenful-paint<\/code>. I know that a lot happens between the time a user navigates to a page and stuff starts painting, but I didn\u2019t know there was a difference between these two metrics.<\/p>\n<p>Here\u2019s how <a href=\"https:\/\/w3c.github.io\/paint-timing\/#first-paint-and-first-contentful-paint\">the spec<\/a> explains it:<\/p>\n<blockquote><p>\u201cThe primary difference between the two metrics is that [First Paint] marks the first time the browser renders anything for a given document. By contrast, [First Contentful Paint] marks the time when the browser renders the first bit of image or text content from the DOM.\u201d<\/p><\/blockquote>\n<p>As it turns out, the first paint and FCP data I got back in that last example are identical. Since first paint can be <a href=\"https:\/\/www.debugbear.com\/docs\/web-performance-metrics?utm_campaign=sm-4#first-paint-fp\"><em>anything<\/em> that prevents a blank screen<\/a>, e.g., a background color, I think that the identical results mean that whatever content is first painted to the screen just so happens to also be the first contentful paint.<\/p>\n<p>But there\u2019s apparently a lot more nuance to it, as Chrome measures FCP differently based on what version of the browser is in use. <a href=\"https:\/\/chromium.googlesource.com\/chromium\/src\/+\/refs\/heads\/main\/docs\/speed\/metrics_changelog\/fcp.md\">Google keeps a full record of the changelog<\/a> for reference, so that\u2019s something to keep in mind when evaluating results, especially if you find yourself with different results from others on your team.<\/p>\n<h2 id=\"reporting-cumulative-layout-shift\">Reporting Cumulative Layout Shift<\/h2>\n<p>How much does the page shift around as elements are painted to it? Of course, we can get that from the Performance API! Instead of <code>largest-contentful-paint<\/code> or <code>paint<\/code>, now we\u2019re turning to the <code>layout-shift<\/code> type.<\/p>\n<p>This is where browser support is dicier than other performance metrics. The <code>LayoutShift<\/code> interface is still in \u201cexperimental\u201d status at this time, with <a href=\"https:\/\/caniuse.com\/mdn-api_layoutshift\">Chromium browsers being the sole group of supporters<\/a>.<\/p>\n<p>As it currently stands, <code>LayoutShift<\/code> opens up several pieces of information, including a <code>value<\/code> representing the amount of shifting, as well as the <code>sources<\/code> causing it to happen. More than that, we can tell if any user interactions took place that would affect the CLS value, such as zooming, changing browser size, or actions like <code>keydown<\/code>, <code>pointerdown<\/code>, and <code>mousedown<\/code>. This is the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/LayoutShift\/lastInputTime\"><code>lastInputTime<\/code> property<\/a>, and there\u2019s an accompanying <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/LayoutShift\/hadRecentInput\"><code>hasRecentInput<\/code> boolean<\/a> that returns <code>true<\/code> if the <code>lastInputTime<\/code> is less than <code>500ms<\/code>.<\/p>\n<p>Got all that? We can use this to both see how much shifting takes place during page load and identify the culprits while excluding any shifts that are the result of user interactions.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">const observer = new PerformanceObserver((list) => {\n let cumulativeLayoutShift = 0;\n list.getEntries().forEach((entry) => {\n \/\/ Don't count if the layout shift is a result of user interaction.\n if (!entry.hadRecentInput) {\n cumulativeLayoutShift += entry.value;\n }\n console.log({ entry, cumulativeLayoutShift });\n });\n});\n\n\/\/ Call the Observer.\nobserver.observe({ type: \"layout-shift\", buffered: true });\n<\/code><\/pre>\n<\/div>\n<p>Given the experimental nature of this one, here\u2019s what an <code>entry<\/code> object looks like when we query it:<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/4-tree-outline.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"276\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/4-tree-outline.png\" alt=\"Tree outline showing the object properties and values for entries in the LayoutShift class produced by a query.\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/4-tree-outline.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>Pretty handy, right? Not only are we able to see how much shifting takes place (<code>0.128<\/code>) and which element is moving around (<code>article.a.main<\/code>), but we have the exact coordinates of the element\u2019s box from where it starts to where it ends.<\/p>\n<h2 id=\"reporting-interaction-to-next-paint\">Reporting Interaction To Next Paint<\/h2>\n<p>This is the new kid on the block that got my mind wondering about the Performance API in the first place. It\u2019s been possible for some time now to measure INP as it transitions to replace First Input Delay as a Core Web Vitals metric in March 2024. When we\u2019re talking about INP, we\u2019re talking about measuring the time between a user interacting with the page and the page responding to that interaction.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/5-timeline-illustration-tasks.jpg\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"393\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/5-timeline-illustration-tasks.jpg\" alt=\"Timeline illustration showing the tasks in between input delay and presentation delay in response to user interaction.\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/5-timeline-illustration-tasks.jpg\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>We need to hook into the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/PerformanceEventTiming\"><code>PerformanceEventTiming<\/code> class<\/a> for this one. And there\u2019s so much we can dig into when it comes to user interactions. Think about it! There\u2019s what type of event happened (<code>entryType<\/code> and <code>name<\/code>), when it happened (<code>startTime<\/code>), what element triggered the interaction (<code>interactionId<\/code>, experimental), and when processing the interaction starts (<code>processingStart<\/code>) and ends (<code>processingEnd<\/code>). There\u2019s also a way to exclude interactions that can be canceled by the user (<code>cancelable<\/code>).<\/p>\n<pre><code class=\"language-javascript\">const observer = new PerformanceObserver((list) => {\n list.getEntries().forEach((entry) => {\n \/\/ Alias for the total duration.\n const duration = entry.duration;\n \/\/ Calculate the time before processing starts.\n const delay = entry.processingStart - entry.startTime;\n \/\/ Calculate the time to process the interaction.\n const lag = entry.processingStart - entry.startTime;\n\n \/\/ Don't count interactions that the user can cancel.\n if (!entry.cancelable) {\n console.log(`INP Duration: ${duration}`);\n console.log(`INP Delay: ${delay}`);\n console.log(`Event handler duration: ${lag}`);\n }\n });\n});\n\n\/\/ Call the Observer.\nobserver.observe({ type: \"event\", buffered: true });\n<\/code><\/pre>\n<h2 id=\"reporting-long-animation-frames-loafs\">Reporting Long Animation Frames (LoAFs)<\/h2>\n<p>Let\u2019s build off that last one. We can now track INP scores on our website and break them down into specific components. But what code is actually running and causing those delays?<\/p>\n<p>The <a href=\"https:\/\/www.debugbear.com\/blog\/long-animation-frames\/?utm_campaign=sm-4\">Long Animation Frames API<\/a> was developed to help answer that question. It won\u2019t land in Chrome stable until mid-March 2024, but you can already use it in Chrome Canary.<\/p>\n<p>A <code>long-animation-frame<\/code> entry is reported every time the browser couldn\u2019t render page content immediately as it was busy with other processing tasks. We get an overall <code>duration<\/code> for the long frame but also a <code>duration<\/code> for different <code>scripts<\/code> involved in the processing.<\/p>\n<div class=\"break-out\">\n<pre><code class=\"language-javascript\">const observer = new PerformanceObserver((list) => {\n list.getEntries().forEach((entry) => {\n if (entry.duration > 50) {\n \/\/ Log the overall duration of the long frame.\n console.log(`Frame took ${entry.duration} ms`)\n console.log(`Contributing scripts:`)\n \/\/ Log information on each script in a table.\n entry.scripts.forEach(script => {\n console.table({\n \/\/ URL of the script where the processing starts\n sourceURL: script.sourceURL,\n \/\/ Total time spent on this sub-task\n duration: script.duration,\n \/\/ Name of the handler function\n functionName: script.sourceFunctionName,\n \/\/ Why was the handler function called? For example, \n \/\/ a user interaction or a fetch response arriving.\n invoker: script.invoker\n })\n })\n }\n });\n});\n\n\/\/ Call the Observer.\nobserver.observe({ type: \"long-animation-frame\", buffered: true });\n<\/code><\/pre>\n<\/div>\n<p>When an INP interaction takes place, we can find the closest long animation frame and investigate what processing delayed the page response.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/6-animation-frames-data-chrome-devtools-console.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"344\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/6-animation-frames-data-chrome-devtools-console.png\" alt=\"Long animation frames data the Chrome DevTools Console\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/6-animation-frames-data-chrome-devtools-console.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<h2 id=\"there-s-a-package-for-this\">There\u2019s A Package For This<\/h2>\n<p>The Performance API is so big and so powerful. We could easily spend an entire bootcamp learning all of the interfaces and what they provide. There\u2019s network timing, navigation timing, resource timing, and plenty of custom reporting features available on top of the Core Web Vitals we\u2019ve looked at.<\/p>\n<p>If CWVs are what you\u2019re really after, then you might consider looking into the <a href=\"https:\/\/github.com\/GoogleChrome\/web-vitals\">web-vitals library<\/a> to wrap around the browser Performance APIs.<\/p>\n<p>Need a CWV metric? All it takes is a single function.<\/p>\n<pre><code class=\"language-javascript\">webVitals.getINP(function(info) {\n console.log(info)\n}, { reportAllChanges: true });\n<\/code><\/pre>\n<p>Boom! That <code>reportAllChanges<\/code> property? That\u2019s a way of saying we only want to report data every time the metric changes instead of only when the metric reaches its final value. For example, as long as the page is open, there\u2019s always a chance that the user will encounter an even slower interaction than the current INP interaction. So, without <code>reportAllChanges<\/code>, we\u2019d only see the INP reported when the page is closed (or when it\u2019s hidden, e.g., if the user switches to a different browser tab).<\/p>\n<p>We can also report purely on the difference between the preliminary results and the resulting changes. From the <a href=\"https:\/\/github.com\/GoogleChrome\/web-vitals?tab=readme-ov-file#report-only-the-delta-of-changes\">web-vitals docs<\/a>:<\/p>\n<pre><code class=\"language-javascript\">function logDelta({ name, id, delta }) {\n console.log(`${name} matching ID ${id} changed by ${delta}`);\n}\n\nonCLS(logDelta);\nonINP(logDelta);\nonLCP(logDelta);\n<\/code><\/pre>\n<h2 id=\"measuring-is-fun-but-monitoring-is-better\">Measuring Is Fun, But Monitoring Is Better<\/h2>\n<p>All we\u2019ve done here is scratch the surface of the Performance API as far as programmatically reporting Core Web Vitals metrics. It\u2019s fun to play with things like this. There\u2019s even a slight feeling of <em>power<\/em> in being able to tap into this information on demand.<\/p>\n<p>At the end of the day, though, you\u2019re probably just as interested <a href=\"https:\/\/www.smashingmagazine.com\/2023\/08\/running-page-speed-test-monitoring-versus-measuring\/\">in <em>monitoring<\/em> performance as you are in <em>measuring<\/em> it<\/a>. We could do a deep dive and detail what a performance dashboard powered by the Performance API is like, complete with historical records that indicate changes over time. That\u2019s ultimately the sort of thing we can build on this — we can build our own real user monitoring (RUM) tool or perhaps compare Performance API values against historical data from the <a href=\"https:\/\/developer.chrome.com\/docs\/crux\/history-api\">Chrome User Experience Report<\/a> <a href=\"https:\/\/developer.chrome.com\/docs\/crux\/bigquery\">(CrUX)<\/a>.<\/p>\n<p>Or perhaps you want a solution right now without stitching things together. That\u2019s what you\u2019ll get from a paid commercial service like <a href=\"https:\/\/www.debugbear.com\/?utm_campaign=sm-4\">DebugBear<\/a>. All of this is already baked right in with all the metrics, historical data, and charts you need to gain insights into the overall performance of a site over time\u2026 <a href=\"https:\/\/www.debugbear.com\/real-user-monitoring\/?utm_campaign=sm-4\">and in real-time, monitoring real users<\/a>.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/7-debugbear-largest-contentful-paint-dashboard.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"474\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/7-debugbear-largest-contentful-paint-dashboard.png\" alt=\"DebugBear Largest Contentful Paint dashboard showing overall speed, a histogram, a timeline, and a performance breakdown of the most popular pages.\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/7-debugbear-largest-contentful-paint-dashboard.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>DebugBear can help you identify why users are having slow experiences on any given page. If there is slow INP, what page elements are these users interacting with? What elements often shift around on the page and cause high CLS? Is the LCP typically an image, a heading, or something else? And does the type of LCP element impact the LCP score?<\/p>\n<p>To help explain INP scores, DebugBear also supports the upcoming Long Animation Frames API we looked at, allowing you to see what code is responsible for interaction delays.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/8-table-css-selectors.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"316\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/8-table-css-selectors.png\" alt=\"Table showing CSS selectors identifying different page elements that users have interacted with, along with their INP score.\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/8-table-css-selectors.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>The Performance API can also report a list of all resource requests on a page. DebugBear uses this information to show a <a href=\"https:\/\/www.debugbear.com\/docs\/waterfall\/?utm_campaign=sm-4\">request waterfall chart<\/a> that tells you not just when different resources are loaded but also whether the resources were render-blocking, loaded from the cache or whether an image resource is used for the LCP element.<\/p>\n<p>In this screenshot, the blue line shows the FCP, and the red line shows the LCP. We can see that the LCP happens right after the LCP image request, marked by the blue \u201cLCP\u201d badge, has finished.<\/p>\n<figure class=\"\n \n break-out article__image\n \n \n \"><\/p>\n<p> <a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/9-request-waterfall-visualization.png\"><\/p>\n<p> <img decoding=\"async\" loading=\"lazy\" width=\"800\" height=\"413\" src=\"https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_80\/w_400\/https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/9-request-waterfall-visualization.png\" alt=\"A request waterfall visualization showing what resources are loaded by a website and when they are loaded.\" \/><\/p>\n<p> <\/a><figcaption class=\"op-vertical-bottom\">\n (<a href=\"https:\/\/files.smashing.media\/articles\/reporting-core-web-vitals-performance-api\/9-request-waterfall-visualization.png\">Large preview<\/a>)<br \/>\n <\/figcaption><\/figure>\n<p>DebugBear offers a <a href=\"https:\/\/www.debugbear.com\/signup\/?utm_campaign=sm-4\">14-day free trial<\/a>. See how fast your website is, what\u2019s slowing it down, and how you can improve your Core Web Vitals. You\u2019ll also get monitoring alerts, so if there\u2019s a web vitals regression, you\u2019ll find out before it starts impacting Google search results.<\/p>\n<div class=\"signature\">\n <img decoding=\"async\" src=\"https:\/\/www.smashingmagazine.com\/images\/logo\/logo--red.png\" alt=\"Smashing Editorial\" width=\"35\" height=\"46\" loading=\"lazy\" \/><br \/>\n <span>(yk)<\/span>\n<\/div>\n<\/article>\n","protected":false},"excerpt":{"rendered":"<p>Reporting Core Web Vitals With The Performance API Reporting Core Web Vitals With The Performance API Geoff Graham 2024-02-27T12:00:00+00:00 2024-08-30T10:05:08+00:00 This article is sponsored by DebugBear There\u2019s quite a buzz in the performance community with the Interaction to Next Paint (INP) metric becoming an official Core Web Vitals (CWV) metric in a few short weeks. […]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-503","post","type-post","status-publish","format-standard","hentry","category-performance"],"_links":{"self":[{"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/posts\/503"}],"collection":[{"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/comments?post=503"}],"version-history":[{"count":1,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/posts\/503\/revisions"}],"predecessor-version":[{"id":504,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/posts\/503\/revisions\/504"}],"wp:attachment":[{"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/media?parent=503"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/categories?post=503"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/upprofits.net\/index.php\/wp-json\/wp\/v2\/tags?post=503"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}