<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://web.dev/</id>
  <title>Philip Walton on web.dev</title>
  <updated>2026-04-15T23:21:06Z</updated>
  <author>
    <name>Philip Walton</name>
  </author>
  <link href="https://web.dev/authors/philipwalton/feed.xml" rel="self"/>
  <link href="https://web.dev/"/>
  <icon>https://web-dev.imgix.net/image/admin/ovBM8MF9rYDxVVHUVlcG.jpg?auto=format</icon>
  <logo>https://web.dev/images/shared/rss-banner.png</logo>
  <subtitle>Engineer at Google working on the Web Platform</subtitle>
  
  
  <entry>
    <title>Our top Core Web Vitals recommendations for 2023</title>
    <link href="https://web.dev/top-cwv-2023/"/>
    <updated>2023-01-10T00:00:00Z</updated>
    <id>https://web.dev/top-cwv-2023/</id>
    <content type="html" mode="escaped">&lt;p&gt;Over the years, we at Google have made a lot of recommendations to web developers on how to improve performance.&lt;/p&gt;
&lt;p&gt;While each of these recommendations, individually, may improve performance for many sites, the full set of recommendations is admittedly overwhelming and, realistically, there&#39;s no way any one person or site could follow all of them.&lt;/p&gt;
&lt;p&gt;Unless web performance is your day job, it&#39;s probably not obvious which recommendations are going to have the largest positive impact on your site. For example, you might have read that implementing critical CSS can improve load performance, and you may have also heard that it&#39;s important to optimize your images. But, if you don&#39;t have time to work on both things, how would you decide which one to pick?&lt;/p&gt;
&lt;p&gt;On the Chrome team, we&#39;ve spent the last year trying to answer this question: &lt;em&gt;what are the most important recommendations we can give to developers to help them improve performance for their users?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To adequately answer this question we have to consider not just the technical merits of any given recommendation, but also human and organizational factors that influence the likelihood that developers will actually be able to adopt these recommendations. In other words, some recommendations may be hugely impactful in theory, but in reality very few sites will have the time or resources to implement them. Similarly, some recommendations are critical, but most websites are already following these practices.&lt;/p&gt;
&lt;p&gt;In short, we wanted our list of top web performance recommendations to focus on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recommendations we believe will have the &lt;strong&gt;largest real-world impact&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Recommendations that are &lt;strong&gt;relevant and applicable to most sites&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Recommendations that are &lt;strong&gt;realistic for most developers to implement&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Over the past year we&#39;ve spent a lot of time auditing the full set of performance recommendations we make, and assessing each of them (both qualitatively and quantitatively) against the above three criteria.&lt;/p&gt;
&lt;p&gt;This post outlines our top recommendations to improve performance for each of the &lt;a href=&quot;https://web.dev/vitals/#core-web-vitals&quot;&gt;Core Web Vitals&lt;/a&gt; metrics. If you&#39;re new to web performance, or if you&#39;re trying to decide what will give you the biggest bang for your buck, we think these recommendations are the best place to start.&lt;/p&gt;
&lt;h2 id=&quot;largest-contentful-paint-lcp&quot;&gt;Largest Contentful Paint (LCP) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#largest-contentful-paint-lcp&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our first set of recommendations are for &lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;, which is a measure of load performance. Of the three Core Web Vitals metrics, LCP is the one that the largest number of sites struggle with—only &lt;a href=&quot;https://datastudio.google.com/s/nw4gcbKA5o4&quot; rel=&quot;noopener&quot;&gt;about half&lt;/a&gt; of all sites on the web today meet the &lt;a href=&quot;https://web.dev/lcp/#what-is-a-good-lcp-score&quot;&gt;recommended threshold&lt;/a&gt;—so let&#39;s start there.&lt;/p&gt;
&lt;h3 id=&quot;ensure-the-lcp-resource-is-discoverable-from-the-html-source&quot;&gt;Ensure the LCP resource is discoverable from the HTML source &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#ensure-the-lcp-resource-is-discoverable-from-the-html-source&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;According to the &lt;a href=&quot;https://almanac.httparchive.org/en/2022/&quot; rel=&quot;noopener&quot;&gt;2022 Web Almanac&lt;/a&gt; by HTTP Archive, &lt;a href=&quot;https://almanac.httparchive.org/en/2022/performance#fig-8&quot; rel=&quot;noopener&quot;&gt;72%&lt;/a&gt; of mobile pages have an image as their LCP element, which means that for most sites to optimize their LCP, they&#39;ll need to ensure those images can load quickly.&lt;/p&gt;
&lt;p&gt;What may not be obvious to many developers is that the time it takes to load an image is just one part of the challenge. Another critical part is the time &lt;em&gt;before&lt;/em&gt; an image starts loading, and HTTP Archive data suggests that&#39;s actually where many sites get tripped up.&lt;/p&gt;
&lt;p&gt;In fact, of the pages where the LCP element was an image, &lt;a href=&quot;https://almanac.httparchive.org/en/2022/performance#lcp-static-discoverability&quot; rel=&quot;noopener&quot;&gt;39%&lt;/a&gt; of those images had source URLs that were not &lt;a href=&quot;https://web.dev/optimize-lcp/#optimize-when-the-resource-is-discovered&quot;&gt;discoverable&lt;/a&gt; from the HTML document source. In other words, those URLs were not found in standard HTML attributes (such as &lt;code&gt;&amp;lt;img src=&amp;quot;...&amp;quot;&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;link rel=&amp;quot;preload&amp;quot; href=&amp;quot;...&amp;quot;&amp;gt;&lt;/code&gt;), which would allow the browser to quickly discover them and start loading them right away.&lt;/p&gt;
&lt;p&gt;If a page needs to wait for CSS or JavaScript files to be fully downloaded, parsed, and processed before the image can even start loading, it may already be too late.&lt;/p&gt;
&lt;p&gt;As a general rule, if your LCP element is an image, the image&#39;s URL should always be discoverable from the HTML source. Some tips to make that possible are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Load the image using an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element with the &lt;code&gt;src&lt;/code&gt; or &lt;code&gt;srcset&lt;/code&gt; attribute.&lt;/strong&gt; Do not use non-standard attributes like &lt;code&gt;data-src&lt;/code&gt; that require JavaScript in order to render, as that will always be slower. &lt;a href=&quot;https://almanac.httparchive.org/en/2022/performance#lcp-lazy-loading&quot; rel=&quot;noopener&quot;&gt;9%&lt;/a&gt; of pages obscure their LCP image behind &lt;code&gt;data-src&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prefer server-side rendering (SSR) over client-side rendering (CSR),&lt;/strong&gt; as SSR implies that the full page markup (including the image) is present in the HTML source. CSR solutions require JavaScript to run before the image can be discovered.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If your image needs to be referenced from an external CSS or JS file, you can still include it in the HTML source via a &lt;code&gt;&amp;lt;link rel=&amp;quot;preload&amp;quot;&amp;gt;&lt;/code&gt; tag.&lt;/strong&gt; Note that images referenced by inline styles are not discoverable by the browser&#39;s &lt;a href=&quot;https://web.dev/preload-scanner/&quot;&gt;preload scanner&lt;/a&gt;, so even though they&#39;re found in the HTML source, discovery of them might still be blocked on the loading of other resources, so preloading can help in these cases.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To help you understand if your LCP image has discoverability problems, Lighthouse will be releasing a &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse/issues/13738&quot; rel=&quot;noopener&quot;&gt;new audit&lt;/a&gt; in version 10.0 (expected January 2023).&lt;/p&gt;
&lt;p&gt;Ensuring the LCP resource is discoverable from the HTML source can lead to measurable improvements and it also unlocks additional opportunities to prioritize the resource, which is our next recommendation.&lt;/p&gt;
&lt;h3 id=&quot;ensure-the-lcp-resource-is-prioritized&quot;&gt;Ensure the LCP resource is prioritized &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#ensure-the-lcp-resource-is-prioritized&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Making sure the LCP resource can be discovered from the HTML source is a critical first step in ensuring the LCP resource can start loading early, but another important step is ensuring that the loading of that resource is &lt;a href=&quot;https://web.dev/optimize-lcp/#optimize-the-priority-the-resource-is-given&quot;&gt;prioritized&lt;/a&gt; and doesn&#39;t get queued behind a bunch of other, less important resources.&lt;/p&gt;
&lt;p&gt;For example, even if your LCP image is present in the HTML source using a standard &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag, if your page includes a dozen &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of your document before that &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag, it may be a while before your image resource starts loading.&lt;/p&gt;
&lt;p&gt;The easiest way to solve this problem is to provide a hint to the browser about what resources are the highest priority by setting the new &lt;a href=&quot;https://web.dev/fetch-priority/&quot;&gt;&lt;code&gt;fetchpriority=&amp;quot;high&amp;quot;&lt;/code&gt;&lt;/a&gt; attribute on the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag that loads your LCP image. This instructs the browser to load it earlier, rather than waiting for those scripts to complete.&lt;/p&gt;
&lt;p&gt;According to the Web Almanac, only &lt;a href=&quot;https://almanac.httparchive.org/en/2022/performance#lcp-prioritization&quot; rel=&quot;noopener&quot;&gt;0.03%&lt;/a&gt; of eligible pages are taking advantage of this new API, meaning there is plenty of opportunity for most sites on the web to improve LCP with very little work. While the &lt;code&gt;fetchpriority&lt;/code&gt; attribute is currently only supported in Chromium-based browsers, this API is a progressive enhancement that other browsers just ignore, so we strongly recommend developers use it now.&lt;/p&gt;
&lt;p&gt;For non-Chromium browsers, the only way to ensure the LCP resource is prioritized above other resources is to reference it earlier in the document. Using the example again of a site with lots of &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the document, if you wanted to ensure your LCP resource was prioritized ahead of those script resources, you could add a &lt;code&gt;&amp;lt;link rel=&amp;quot;preload&amp;quot;&amp;gt;&lt;/code&gt; tag before any of those scripts, or you could move those scripts to below the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; later in the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. While this works, it&#39;s less ergonomic than using &lt;code&gt;fetchpriority&lt;/code&gt;, so we hope other browsers add support soon.&lt;/p&gt;
&lt;p&gt;Another critical aspect of prioritizing the LCP resource is to ensure you don&#39;t do anything that causes it to be &lt;strong&gt;deprioritized&lt;/strong&gt;, such as adding the &lt;code&gt;loading=&amp;quot;lazy&amp;quot;&lt;/code&gt; attribute. Today, &lt;a href=&quot;https://almanac.httparchive.org/en/2022/performance#lcp-lazy-loading&quot; rel=&quot;noopener&quot;&gt;10%&lt;/a&gt; of pages actually set &lt;code&gt;loading=&amp;quot;lazy&amp;quot;&lt;/code&gt; on their LCP image. Beware of image optimization solutions that indiscriminately apply lazy-loading behavior to all images. If they provide a way to override that behavior, be sure to use it for the LCP image. If you&#39;re not sure which image will be the LCP, try using heuristics to pick a reasonable candidate.&lt;/p&gt;
&lt;p&gt;Deferring non-critical resources is another way to effectively boost the relative priority of the LCP resource. For example, scripts that are not powering the user interface (like analytics scripts or social widgets) can be safely postponed until after the &lt;code&gt;load&lt;/code&gt; event fires, which ensures they won&#39;t compete with other critical resources (such as the LCP resource) for network bandwidth.&lt;/p&gt;
&lt;p&gt;To summarize, you should follow these best practices to ensure that the LCP resource is loaded early, and at high priority:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Add &lt;code&gt;fetchpriority=&amp;quot;high&amp;quot;&lt;/code&gt; to the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag of your LCP image.&lt;/strong&gt; If the LCP resource is loaded via a&lt;code&gt; &amp;lt;link rel=&amp;quot;preload&amp;quot;&amp;gt;&lt;/code&gt; tag, fear not because you can also set &lt;code&gt;fetchpriority=&amp;quot;high&amp;quot;&lt;/code&gt; on that!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never set &lt;code&gt;loading=&amp;quot;lazy&amp;quot;&lt;/code&gt; on the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag of your LCP image.&lt;/strong&gt; Doing this will deprioritize your image and delay when it starts loading.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Defer non-critical resources when possible.&lt;/strong&gt; Either by moving them to the end of your document, using native lazy-loading for &lt;a href=&quot;https://web.dev/browser-level-image-lazy-loading/&quot;&gt;images&lt;/a&gt; or &lt;a href=&quot;https://web.dev/iframe-lazy-loading/&quot;&gt;iframes&lt;/a&gt;, or loading them asynchronously via JavaScript.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;use-a-cdn-to-optimize-document-and-resource-ttfb&quot;&gt;Use a CDN to optimize document and resource TTFB &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#use-a-cdn-to-optimize-document-and-resource-ttfb&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The previous two recommendations focused on making sure your LCP resource is discovered early and prioritized so it can start loading right away. The final piece to this puzzle is making sure the initial document response arrives as quickly as possible too.&lt;/p&gt;
&lt;p&gt;The browser cannot start loading any subresources until it receives the first byte of the initial HTML document response, and the sooner that happens, the sooner everything else can start happening as well.&lt;/p&gt;
&lt;p&gt;This time is known as &lt;a href=&quot;https://web.dev/ttfb/&quot;&gt;Time to First Byte (TTFB)&lt;/a&gt;, and the best way to reduce TTFB is to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Serve your content as geographically close to your users as possible&lt;/li&gt;
&lt;li&gt;Cache that content so recently-requested content can be served again quickly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The best way to do both of these things is to &lt;a href=&quot;https://web.dev/content-delivery-networks/&quot;&gt;use a CDN&lt;/a&gt;. CDNs distribute your resources to edge servers, which are spread across the globe, thus limiting the distance those resources have to travel over the wire to your users. CDNs also usually have fine-grained caching controls that can be customized and optimized for your site&#39;s needs.&lt;/p&gt;
&lt;p&gt;Many developers are familiar with using a CDN to host static assets, but CDNs can serve and cache HTML documents as well, even those that are dynamically generated.&lt;/p&gt;
&lt;p&gt;According to the Web Almanac, only &lt;a href=&quot;https://almanac.httparchive.org/en/2022/cdn#cdn-adoption&quot; rel=&quot;noopener&quot;&gt;29%&lt;/a&gt; of HTML document requests were served from a CDN, which means there is significant opportunity for sites to claim additional savings.&lt;/p&gt;
&lt;p&gt;Some tips for configuring your CDNs are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Consider increasing how long content is cached for (for example, is it actually critical that content is always fresh? Or can it be a few minutes stale?).&lt;/li&gt;
&lt;li&gt;Consider maybe even caching content indefinitely, and then purging the cache if/when you make an update.&lt;/li&gt;
&lt;li&gt;Explore whether you can move dynamic logic currently running on your origin server to the &lt;a href=&quot;https://en.wikipedia.org/wiki/Edge_computing&quot; rel=&quot;noopener&quot;&gt;edge&lt;/a&gt; (a feature of most modern CDNs).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In general, any time you can serve content directly from the edge (avoiding a trip to your origin server) it&#39;s a performance win. And even in cases where you &lt;em&gt;do&lt;/em&gt; have to make the journey all the way back to your origin server, CDNs are generally optimized to do that much more quickly, so it&#39;s a win either way.&lt;/p&gt;
&lt;h2 id=&quot;cumulative-layout-shift-cls&quot;&gt;Cumulative Layout Shift (CLS) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#cumulative-layout-shift-cls&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The next set of recommendations are for &lt;a href=&quot;https://web.dev/cls/&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt;, which is a measure of visual stability on web pages. While CLS has &lt;a href=&quot;https://datastudio.google.com/s/gFjrTptD140&quot; rel=&quot;noopener&quot;&gt;improved a lot&lt;/a&gt; on the web since 2020, about a quarter of websites still do not meet the &lt;a href=&quot;https://web.dev/cls/#what-is-a-good-cls-score&quot;&gt;recommended threshold&lt;/a&gt;, so there remains a big opportunity for many sites to improve their user experience.&lt;/p&gt;
&lt;h3 id=&quot;set-explicit-sizes-on-any-content-loaded-from-the-page&quot;&gt;Set explicit sizes on any content loaded from the page &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#set-explicit-sizes-on-any-content-loaded-from-the-page&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/cls/#layout-shifts-in-detail&quot;&gt;Layout shifts&lt;/a&gt; usually happen when existing content moves after other content finishes loading. Therefore, the primary way to mitigate this is to reserve any required space in advance as much as possible.&lt;/p&gt;
&lt;p&gt;The most straightforward way to fix layout shifts caused by unsized images is to &lt;strong&gt;explicitly set &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes&lt;/strong&gt; (or equivalent CSS properties). However, according to HTTP Archive, &lt;a href=&quot;https://almanac.httparchive.org/en/2022/performance#explicit-dimensions&quot; rel=&quot;noopener&quot;&gt;72%&lt;/a&gt; of pages have at least one unsized image. Without an explicit size, browsers will initially set a default height of &lt;code&gt;0px&lt;/code&gt; and may cause a noticeable layout shift when the image is finally loaded and the dimensions are discovered. This represents both a huge opportunity for the collective web—and that opportunity requires much less effort than some of the other recommendations suggested in this article.&lt;/p&gt;
&lt;p&gt;It&#39;s also important to keep in mind that images are not the only contributors to CLS. Layout shifts may be caused by other content that typically loads in after the page is initially rendered, including third-party ads or embedded videos. The &lt;a href=&quot;https://web.dev/aspect-ratio/&quot;&gt;&lt;code&gt;aspect-ratio&lt;/code&gt;&lt;/a&gt; property can help combat this. It&#39;s a relatively new CSS feature that allows developers to explicitly provide an aspect ratio to images as well as non-image elements. This will allow you to set a dynamic  &lt;code&gt;width&lt;/code&gt; (for example based on screen size), and have the browser automatically calculate the appropriate height, in much the same way as they do for images with dimensions.&lt;/p&gt;
&lt;p&gt;Sometimes it&#39;s not possible to know the exact size of dynamic content since it is, by its very nature, dynamic. However, even if you don&#39;t know the exact size, you can still take steps to reduce the severity of layout shifts. &lt;strong&gt;Setting a sensible &lt;code&gt;min-height&lt;/code&gt;&lt;/strong&gt; is almost always better than allowing the browser to use the default height of &lt;code&gt;0px&lt;/code&gt; for an empty element. Using a &lt;code&gt;min-height&lt;/code&gt; is also usually an easy fix as it still allows the container to grow to the final content height if needed—it has just reduced that amount of growth from the full amount to a hopefully more tolerable level.&lt;/p&gt;
&lt;h3 id=&quot;ensure-pages-are-eligible-for-bfcache&quot;&gt;Ensure pages are eligible for bfcache &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#ensure-pages-are-eligible-for-bfcache&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Browsers use a navigation mechanism called the &lt;a href=&quot;https://web.dev/bfcache/&quot;&gt;back/forward cache&lt;/a&gt;—or bfcache for short—to instantly load a page from earlier or later in the browser history directly from a memory snapshot.&lt;/p&gt;
&lt;p&gt;The bfcache is a significant browser-level performance optimization, and it entirely eliminates the layout shifts during page load, which for many sites is where most of their CLS occurs. The introduction of the bfcache caused &lt;a href=&quot;https://twitter.com/anniesullie/status/1491399685961293828?s=20&amp;amp;t=k7JgTjdO21uMpeOuOofroA&quot; rel=&quot;noopener&quot;&gt;the biggest improvement in CLS&lt;/a&gt; that we saw in 2022.&lt;/p&gt;
&lt;p&gt;Despite this, &lt;a href=&quot;https://almanac.httparchive.org/en/2022/performance#bfcache-eligibility&quot; rel=&quot;noopener&quot;&gt;a significant number of websites&lt;/a&gt; are ineligible for the bfcache and so are missing out on this free web performance win for a significant number of navigations. Unless your page is loading sensitive information that you don&#39;t want to be restored from memory, you&#39;ll want to make sure that your pages are eligible.&lt;/p&gt;
&lt;p&gt;Site owners should check that their pages are &lt;a href=&quot;https://web.dev/bfcache/#optimize-your-pages-for-bfcache&quot;&gt;eligible for the bfcache&lt;/a&gt; and work on any reasons why they are not. Chrome already &lt;a href=&quot;https://web.dev/bfcache/#test-to-ensure-your-pages-are-cacheable&quot;&gt;has a bfcache tester in DevTools&lt;/a&gt; and this year we plan to enhance tooling here with &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse/issues/13960&quot; rel=&quot;noopener&quot;&gt;a new Lighthouse audit performing a similar test&lt;/a&gt; and &lt;a href=&quot;https://chromestatus.com/feature/5684908759449600&quot; rel=&quot;noopener&quot;&gt;an API to measure this in the field&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While we have included the bfcache in the CLS section, as we saw the biggest gains there so far, the bfcache will generally also improve other Core Web Vitals too. It is one of &lt;a href=&quot;https://calendar.perfplanet.com/2022/fast-is-good-instant-is-better/&quot; rel=&quot;noopener&quot;&gt;a number of instant navigations&lt;/a&gt; available to drastically improve page navigations.&lt;/p&gt;
&lt;h3 id=&quot;avoid-animationstransitions-that-use-layout-inducing-css-properties&quot;&gt;Avoid animations/transitions that use layout-inducing CSS properties &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#avoid-animationstransitions-that-use-layout-inducing-css-properties&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another common source of layout shifts is when elements are animated. For example, cookie banners or other notification banners that slide in from the top or bottom are often a contributor to CLS. This is particularly problematic when these banners push other content out of the way, but even when they don&#39;t, animating them can still impact CLS.&lt;/p&gt;
&lt;p&gt;While HTTP Archive data can&#39;t conclusively connect animations to layout shifts, the data does show that pages that animate any CSS property that &lt;em&gt;could&lt;/em&gt; affect layout are 15% less likely to have &amp;quot;good&amp;quot; CLS than pages overall. Some properties are associated with worse CLS than others. For instance, pages that animate &lt;code&gt;margin&lt;/code&gt; or &lt;code&gt;border&lt;/code&gt; widths have &amp;quot;poor&amp;quot; CLS at almost twice the rate that pages overall are assessed as poor.&lt;/p&gt;
&lt;p&gt;This is perhaps not surprising, because any time you transition or animate &lt;em&gt;any&lt;/em&gt; layout-inducing CSS property, it will result in &lt;a href=&quot;https://web.dev/cls/#layout-shifts-in-detail&quot;&gt;layout shifts&lt;/a&gt;, and if those layout shifts are not within 500 milliseconds of a user interaction, they will impact CLS.&lt;/p&gt;
&lt;p&gt;What may be surprising to some developers is that this is true even in cases where the element is taken outside of the normal document flow. For example, absolutely positioned elements that animate &lt;code&gt;top&lt;/code&gt; or &lt;code&gt;left&lt;/code&gt; will cause layout shifts, even if they aren&#39;t pushing other content around. However, if instead of animating &lt;code&gt;top&lt;/code&gt; or &lt;code&gt;left&lt;/code&gt; you animate &lt;code&gt;transform:translateX()&lt;/code&gt; or &lt;code&gt;transform:translateY()&lt;/code&gt;, it won&#39;t cause the browser to update page layout and thus won&#39;t produce any layout shifts.&lt;/p&gt;
&lt;p&gt;Preferring animation of CSS properties that can be updated on the browser&#39;s compositor thread has long been &lt;a href=&quot;https://web.dev/animations-guide/&quot;&gt;a performance best practice&lt;/a&gt; because it moves that work onto the GPU and off the main thread. And in addition to it being a general performance best practice, it can also help improve CLS.&lt;/p&gt;
&lt;p&gt;As a general rule, never animate or transition any CSS property that requires the browser to update the page layout, unless you&#39;re doing it in response to a user tap or key press (though &lt;a href=&quot;https://web.dev/cls/#user-initiated-layout-shifts&quot;&gt;not &lt;code&gt;hover&lt;/code&gt;&lt;/a&gt;). And whenever possible, prefer transitions and animations using the CSS &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/transform&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;transform&lt;/code&gt;&lt;/a&gt; property.&lt;/p&gt;
&lt;p&gt;The Lighthouse audit &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/non-composited-animations/&quot; rel=&quot;noopener&quot;&gt;Avoid non-composited animations&lt;/a&gt; will warn when a page animates potentially slow CSS properties.&lt;/p&gt;
&lt;h2 id=&quot;first-input-delay-fid&quot;&gt;First Input Delay (FID) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#first-input-delay-fid&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our last set of recommendations are for &lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt;, which is a measure of a page&#39;s responsiveness to user interactions. While most sites on the web currently &lt;a href=&quot;https://datastudio.google.com/s/vax9YUKhRN0&quot; rel=&quot;noopener&quot;&gt;score very well&lt;/a&gt; on FID, we&#39;ve &lt;a href=&quot;https://web.dev/better-responsiveness-metric/#what-improvements-are-we-considering&quot;&gt;documented&lt;/a&gt; shortcomings of the FID metric in the past, and we believe there is still a lot of opportunity for sites to improve their overall responsiveness to user interactions.&lt;/p&gt;
&lt;p&gt;Our new &lt;a href=&quot;https://web.dev/inp/&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt; metric is a possible successor to FID, and all of the recommendations below apply equally well to both FID and INP. Given that sites &lt;a href=&quot;https://almanac.httparchive.org/en/2022/performance#inp-as-a-hypothetical-cwv-metric&quot; rel=&quot;noopener&quot;&gt;perform worse&lt;/a&gt; on INP than FID, especially on mobile, we encourage developers to seriously consider these responsiveness recommendations, despite having &amp;quot;good&amp;quot; FID.&lt;/p&gt;
&lt;h3 id=&quot;avoid-or-break-up-long-tasks&quot;&gt;Avoid or break up long tasks &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#avoid-or-break-up-long-tasks&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Tasks are any piece of discrete work that the browser does. Tasks include rendering, layout, parsing, and compiling and executing scripts. When tasks become &lt;a href=&quot;https://web.dev/long-tasks-devtools/#what-are-long-tasks&quot;&gt;long tasks&lt;/a&gt;—that is, 50 milliseconds or longer—they block the main thread from being able to respond quickly to user inputs.&lt;/p&gt;
&lt;p&gt;Per the Web Almanac, there&#39;s &lt;a href=&quot;https://almanac.httparchive.org/en/2022/javascript#long-tasksblocking-time&quot; rel=&quot;noopener&quot;&gt;plenty of evidence&lt;/a&gt; to suggest that developers could be doing more to avoid or break up long tasks. While breaking up long tasks may not be as low of an effort as other recommendations in this article, it&#39;s less effort than other techniques not offered in this article.&lt;/p&gt;
&lt;p&gt;While you should always strive to do as little work as possible in JavaScript, you can help the main thread quite a bit by &lt;a href=&quot;https://web.dev/optimize-long-tasks/&quot;&gt;breaking up long tasks into smaller ones&lt;/a&gt;. You can accomplish this by &lt;a href=&quot;https://web.dev/optimize-long-tasks/#use-asyncawait-to-create-yield-points&quot;&gt;yielding to the main thread&lt;/a&gt; often so that rendering updates and other user interactions can occur more quickly.&lt;/p&gt;
&lt;p&gt;Another option is to consider using APIs such as &lt;a href=&quot;https://web.dev/optimize-long-tasks/#yield-only-when-necessary&quot;&gt;&lt;code&gt;isInputPending&lt;/code&gt;&lt;/a&gt; and the &lt;a href=&quot;https://web.dev/optimize-long-tasks/#a-dedicated-scheduler-api&quot;&gt;Scheduler API&lt;/a&gt;. &lt;code&gt;isInputPending&lt;/code&gt; is a function that returns a boolean value that indicates whether a user input is pending. If it returns &lt;code&gt;true&lt;/code&gt;, you can yield to the main thread so it can handle the pending user input.&lt;/p&gt;
&lt;p&gt;The Scheduler API is a more advanced approach, which allows you to schedule work based on a system of priorities that take into account whether the work being done is user-visible or backgrounded.&lt;/p&gt;
&lt;p&gt;By breaking up long tasks, you&#39;re giving the browser more opportunities to fit in critical user-visible work, such as dealing with interactions and any resulting rendering updates.&lt;/p&gt;
&lt;h3 id=&quot;avoid-unnecessary-javascript&quot;&gt;Avoid unnecessary JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#avoid-unnecessary-javascript&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There&#39;s no doubt about it: &lt;a href=&quot;https://almanac.httparchive.org/en/2022/javascript#how-much-javascript-do-we-load&quot; rel=&quot;noopener&quot;&gt;websites are shipping more JavaScript than ever before&lt;/a&gt;, and the trend doesn&#39;t look like it&#39;s changing any time soon. When you ship too much JavaScript, you&#39;re creating an environment where tasks are competing for the main thread&#39;s attention. This can definitely affect your website&#39;s responsiveness, especially during that crucial startup period.&lt;/p&gt;
&lt;p&gt;This is not an unsolvable problem, however. You do have some options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/coverage/&quot; rel=&quot;noopener&quot;&gt;coverage tool&lt;/a&gt; in Chrome DevTools to find unused code in your website&#39;s resources. By reducing the size of the resources you need during startup, you can ensure your website spends less time parsing and compiling code, which leads to a smoother initial user experience.&lt;/li&gt;
&lt;li&gt;Sometimes the unused code you find using the coverage tool is marked &amp;quot;unused&amp;quot; because it wasn&#39;t executed during startup, but is still necessary for some functionality in the future. This is code that you can move to a separate bundle via &lt;a href=&quot;https://web.dev/reduce-javascript-payloads-with-code-splitting/&quot;&gt;code splitting&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you&#39;re using a tag manager, be sure to &lt;a href=&quot;https://web.dev/tag-best-practices/&quot;&gt;periodically check your tags to make sure they are optimized&lt;/a&gt;, or even if they&#39;re still being used. Older tags with unused code can be cleared out to make your tag manager&#39;s JavaScript smaller and more efficient.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;avoid-large-rendering-updates&quot;&gt;Avoid large rendering updates &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#avoid-large-rendering-updates&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;JavaScript isn&#39;t the only thing that can affect your website&#39;s responsiveness. Rendering can be a type of expensive work in its own right—and when large rendering updates happen, they can interfere with your website&#39;s ability to respond to user inputs.&lt;/p&gt;
&lt;p&gt;Optimizing rendering work isn&#39;t a straightforward process, and it often depends on what you&#39;re trying to achieve. Even so, there are some things you can do to ensure that your rendering updates are reasonable, and don&#39;t sprawl into long tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Avoid using &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;requestAnimationFrame()&lt;/code&gt;&lt;/a&gt; for doing any non-visual work. &lt;code&gt;requestAnimationFrame()&lt;/code&gt; calls are handled during the rendering phase of the event loop, and when too much work is done during this step, rendering updates can be delayed. It&#39;s essential that any work you&#39;re doing with &lt;code&gt;requestAnimationFrame()&lt;/code&gt; is reserved strictly for tasks that involve rendering updates.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/dom-size/&quot; rel=&quot;noopener&quot;&gt;Keep your DOM size small&lt;/a&gt;. DOM size and the intensity of layout work are correlated. When the renderer has to update the layout for a very large DOM, the work required to recalculate its layout can increase significantly.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/CSS_Containment&quot; rel=&quot;noopener&quot;&gt;Use CSS containment&lt;/a&gt;. CSS containment relies on the CSS &lt;code&gt;contain&lt;/code&gt; property, which gives instructions to the browser about how to do layout work for the container the &lt;code&gt;contain&lt;/code&gt; property is set on, including even isolating the scope of layout and rendering to a specific root in the DOM. It&#39;s not always an easy process, but by isolating areas containing complex layouts, you can avoid doing layout and rendering work for them that isn&#39;t necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/top-cwv-2023/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Improving page performance can seem like a daunting task, especially given that there is a mountain of guidance across the web to consider. By focusing on these recommendations, however, you can approach the problem with focus and purpose, and hopefully move the needle for your website&#39;s Core Web Vitals.&lt;/p&gt;
&lt;p&gt;While the recommendations listed here are by no means exhaustive, we do believe—based on careful analysis of the state of the web—that these recommendations are the most effective ways that sites can improve their Core Web Vitals performance in 2023.&lt;/p&gt;
&lt;p&gt;If you&#39;d like to go beyond the recommendations listed here, check out these optimization guides for more information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/optimize-lcp/&quot;&gt;Optimize LCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/optimize-cls/&quot;&gt;Optimize CLS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/optimize-fid/&quot;&gt;Optimize FID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/optimize-inp/&quot;&gt;Optimize INP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&#39;s to a new year, and a faster web for all! May your sites be fast for your users in all the ways that matter most.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@devintavery&quot; rel=&quot;noopener&quot;&gt;Devin Avery&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author><author>
      <name>Rick Viscomi</name>
    </author><author>
      <name>Barry Pollard</name>
    </author><author>
      <name>Brendan Kenny</name>
    </author><author>
      <name>Jeremy Wagner</name>
    </author>
  </entry>
  
  <entry>
    <title>Optimize Interaction to Next Paint</title>
    <link href="https://web.dev/optimize-inp/"/>
    <updated>2022-12-08T00:00:00Z</updated>
    <id>https://web.dev/optimize-inp/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Interaction to Next Paint (INP) is a &lt;a href=&quot;https://web.dev/vitals/#pending&quot;&gt;pending&lt;/a&gt; Core Web Vital metric that will &lt;a href=&quot;https://web.dev/inp-cwv/&quot;&gt;replace First Input Delay (FID)&lt;/a&gt; in March 2024. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/inp/&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt; is a &lt;a href=&quot;https://web.dev/vitals/#pending&quot;&gt;pending&lt;/a&gt; Core Web Vital metric that assesses a page&#39;s overall responsiveness to user interactions by observing the latency of all &lt;a href=&quot;https://web.dev/inp/#whats-in-an-interaction&quot;&gt;qualifying interactions&lt;/a&gt; that occur throughout the lifespan of a user&#39;s visit to a page. The final INP value is the longest interaction observed (sometimes ignoring outliers).&lt;/p&gt;
&lt;p&gt;To provide a good user experience, websites should strive to have an Interaction to Next Paint of &lt;strong&gt;200 milliseconds or less&lt;/strong&gt;. To ensure you&#39;re hitting this target for most of your users, a good threshold to measure is the &lt;strong&gt;75th percentile of page loads&lt;/strong&gt;, segmented across mobile and desktop devices.&lt;/p&gt;
&lt;style&gt;
  .inp-mobile {
    display: inline;
  }

  .inp-desktop {
    display: none;
  }

  @media screen and (min-width: 640px) {
    .inp-mobile {
      display: none;
    }

    .inp-desktop {
      display: inline;
    }
  }
&lt;/style&gt;
&lt;figure&gt;
  &lt;svg title=&quot;A diagram of the INP thresholds. An INP at or below 200 milliseconds is considered good. Between 200 and 500 milliseconds suggests a page&#39;s responsiveness needs improvement. Anything over 500 milliseconds means that a page&#39;s responsiveness is poor.&quot; class=&quot;inp-mobile&quot; version=&quot;1.1&quot; id=&quot;Layer_1&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; x=&quot;0&quot; y=&quot;0&quot; viewBox=&quot;0 0 296.6 220.2&quot; style=&quot;enable-background:new 0 0 296.6 220.2&quot; xml:space=&quot;preserve&quot;&gt;&lt;style&gt;.st0{fill:#2979FF} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#0CCE6B} .st2{fill:#191919} .st3{fill-rule:evenodd;clip-rule:evenodd;fill:#FFA400} .st4{fill-rule:evenodd;clip-rule:evenodd;fill:#FF4E42} @media screen and (prefers-color-scheme: light){.st2{fill:#191919}} [data-user-theme=light] .st2 {fill:#191919} @media screen and (prefers-color-scheme: dark){.st2{fill:#fff}} [data-user-theme=dark] .st2{fill:#fff}&lt;/style&gt;&lt;path class=&quot;st0&quot; d=&quot;M83.3 63V0h11.9v63H83.3zm26.3 0V0h13.8l25.3 42.2h.7l-.7-12.1V0h11.8v63H148l-26.8-44.6h-.7l.7 12.1V63h-11.6zm65.4 0V0h22.2c4.1 0 7.7.8 10.9 2.5s5.8 4 7.7 7 2.9 6.4 2.9 10.4c0 3.9-1 7.4-2.9 10.4s-4.5 5.4-7.7 7c-3.2 1.7-6.9 2.5-10.9 2.5h-15.6V28.6h15.9c2.1 0 3.8-.4 5.2-1.2s2.5-1.9 3.2-3.2c.7-1.3 1.1-2.8 1.1-4.3s-.4-2.9-1.1-4.2c-.7-1.3-1.8-2.3-3.2-3.2s-3.1-1.2-5.2-1.2h-10.7V63H175z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st1&quot; d=&quot;M0 137.1h96v38.4H0v-38.4z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st2&quot; d=&quot;M34 161c-.6 0-1.2-.1-1.8-.3-.5-.2-1-.5-1.4-.9-.4-.4-.7-.9-1-1.4-.2-.6-.3-1.1-.3-1.8 0-.6.1-1.2.3-1.8.2-.6.6-1 1-1.4.4-.4.9-.7 1.4-.9.5-.2 1.1-.3 1.8-.3s1.3.1 1.8.3c.6.2 1 .6 1.4 1l-.9.9c-.2-.2-.4-.4-.6-.5-.2-.1-.5-.3-.8-.3-.3-.1-.6-.1-.9-.1-.4 0-.8.1-1.2.2-.4.1-.7.4-1 .6-.3.3-.5.6-.7 1-.2.4-.2.8-.2 1.3s.1.9.2 1.3c.2.4.4.7.7 1 .3.3.6.5 1 .7.4.1.8.2 1.2.2.4 0 .8-.1 1.1-.2.3-.1.6-.3.9-.5.3-.2.5-.5.6-.8.2-.3.3-.6.3-1H34v-1.2h4.2v.7c0 .6-.1 1.2-.3 1.7-.2.5-.5.9-.9 1.3s-.8.6-1.3.8c-.5.3-1.1.4-1.7.4zm9.5 0c-.6 0-1.2-.1-1.8-.3-.5-.2-1-.5-1.4-1-.4-.4-.7-.9-.9-1.4-.2-.5-.3-1.1-.3-1.8 0-.6.1-1.2.3-1.8.2-.5.5-1 .9-1.4.4-.4.9-.7 1.4-1 .5-.2 1.1-.3 1.8-.3.6 0 1.2.1 1.8.3.5.2 1 .6 1.4 1 .4.4.7.9.9 1.4.2.5.3 1.1.3 1.8 0 .6-.1 1.2-.3 1.8-.2.5-.5 1-.9 1.4-.4.4-.9.7-1.4 1-.5.2-1.1.3-1.8.3zm0-1.2c.6 0 1.1-.1 1.6-.4.5-.3.8-.7 1.1-1.1.3-.5.4-1.1.4-1.7 0-.6-.1-1.2-.4-1.7-.3-.5-.7-.9-1.1-1.1-.5-.3-1-.4-1.6-.4-.6 0-1.1.1-1.6.4-.5.3-.8.7-1.1 1.1-.3.5-.4 1-.4 1.7 0 .6.1 1.2.4 1.7.3.5.7.9 1.1 1.1.5.3 1 .4 1.6.4zm9.9 1.2c-.6 0-1.2-.1-1.8-.3-.5-.2-1-.5-1.4-1-.4-.4-.7-.9-.9-1.4-.2-.5-.3-1.1-.3-1.8 0-.6.1-1.2.3-1.8.2-.5.5-1 .9-1.4.4-.4.9-.7 1.4-1 .5-.2 1.1-.3 1.8-.3.6 0 1.2.1 1.8.3.5.2 1 .6 1.4 1 .4.4.7.9.9 1.4.2.5.3 1.1.3 1.8 0 .6-.1 1.2-.3 1.8-.2.5-.5 1-.9 1.4-.4.4-.9.7-1.4 1-.6.2-1.2.3-1.8.3zm0-1.2c.6 0 1.1-.1 1.6-.4.5-.3.8-.7 1.1-1.1.3-.5.4-1.1.4-1.7 0-.6-.1-1.2-.4-1.7-.3-.5-.7-.9-1.1-1.1-.5-.3-1-.4-1.6-.4-.6 0-1.1.1-1.6.4-.5.3-.8.7-1.1 1.1-.3.5-.4 1-.4 1.7 0 .6.1 1.2.4 1.7.3.5.7.9 1.1 1.1.5.3 1 .4 1.6.4zm6 1.1v-8.6h2.8c.9 0 1.7.2 2.3.5.7.4 1.2.9 1.5 1.5.4.6.5 1.4.5 2.2 0 .8-.2 1.6-.5 2.2-.4.6-.9 1.2-1.5 1.5-.6.4-1.4.5-2.3.5h-2.8zm1.3-1.3h1.4c.6 0 1.2-.1 1.7-.4.5-.3.8-.6 1.1-1 .2-.5.4-1 .4-1.6 0-.6-.1-1.2-.4-1.6-.2-.5-.6-.8-1.1-1-.5-.3-1-.4-1.7-.4h-1.4v6z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st3&quot; d=&quot;M96 137.1h105.6v38.4H96v-38.4z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st2&quot; d=&quot;M130.8 153.9v-8.6h1.6l3.9 6.3h.1l-.1-1.7v-4.7h1.3v8.6h-1.4l-4.1-6.6h-.1l.1 1.7v5h-1.3zm8.7 0v-8.6h5.2v1.2h-3.9v6.1h3.9v1.2h-5.2zm.7-3.7V149h4.1v1.2h-4.1zm6.2 3.7v-8.6h5.2v1.2h-3.9v6.1h3.9v1.2h-5.2zm.7-3.7V149h4.1v1.2h-4.1zm6.1 3.7v-8.6h2.8c.9 0 1.7.2 2.3.5.7.4 1.2.9 1.5 1.5.4.6.5 1.4.5 2.2s-.2 1.6-.5 2.2c-.4.6-.9 1.2-1.5 1.5-.6.4-1.4.5-2.3.5h-2.8zm1.3-1.3h1.4c.6 0 1.2-.1 1.7-.4.5-.2.8-.6 1.1-1 .2-.5.4-1 .4-1.6 0-.6-.1-1.2-.4-1.6-.2-.5-.6-.8-1.1-1-.5-.2-1-.4-1.7-.4h-1.4v6zm9.9 1.4c-.5 0-.9-.1-1.3-.3-.4-.2-.8-.4-1.1-.8-.3-.4-.5-.8-.7-1.3l1.2-.5c.1.5.3.9.7 1.2.3.3.7.5 1.2.5.3 0 .5 0 .8-.1.2-.1.4-.2.6-.4s.2-.4.2-.7c0-.3-.1-.5-.2-.7-.1-.2-.3-.3-.6-.5s-.6-.3-1-.5l-.5-.2c-.2-.1-.5-.2-.7-.3-.2-.1-.5-.3-.7-.5-.2-.2-.4-.4-.5-.7-.1-.3-.2-.6-.2-.9 0-.4.1-.8.3-1.2.2-.4.5-.6.9-.8.4-.2.9-.3 1.4-.3.6 0 1 .1 1.4.3.4.2.7.4.9.7.2.3.4.5.4.8l-1.2.5c0-.2-.1-.3-.2-.5s-.3-.3-.5-.4c-.2-.1-.4-.2-.8-.2-.2 0-.5.1-.7.2-.2.1-.4.2-.5.4-.1.2-.2.3-.2.6s.1.6.4.8c.3.2.6.4 1.1.5l.6.2c.3.1.6.2.8.3.3.1.5.3.7.5.2.2.4.4.5.7.1.3.2.6.2 1s-.1.8-.3 1.2c-.2.3-.4.6-.7.8-.3.2-.6.3-.9.4s-.5.2-.8.2zM107 167.9v-8.6h1.3v8.6H107zm3.2 0v-8.6h1.8l2.5 6.5h.1l2.5-6.5h1.8v8.6h-1.3V163l.1-1.5h-.1l-2.5 6.4h-1l-2.5-6.4h-.1l.1 1.5v4.9h-1.4zm10.6 0v-8.6h3c.5 0 1 .1 1.4.3.4.2.8.5 1 .9.3.4.4.9.4 1.4 0 .5-.1 1-.4 1.4-.2.4-.6.7-1 .9-.4.2-.9.3-1.4.3h-2.2v-1.2h2.3c.3 0 .6-.1.8-.2.2-.1.4-.3.5-.5.1-.2.2-.4.2-.7 0-.2-.1-.4-.2-.7-.1-.2-.3-.4-.5-.5-.2-.1-.5-.2-.8-.2h-1.7v7.3h-1.4zm7.2 0v-8.6h3c.5 0 1 .1 1.4.3.4.2.7.5 1 .9.2.4.4.8.4 1.4 0 .3-.1.7-.2 1s-.3.6-.6.8c-.3.2-.6.4-1 .6-.4.1-.8.2-1.2.2h-2.1v-1.2h2.3c.3 0 .5-.1.7-.2.2-.1.4-.3.5-.5.1-.2.2-.5.2-.7 0-.2-.1-.5-.2-.7-.1-.2-.3-.4-.5-.5-.2-.1-.5-.2-.8-.2h-1.7v7.4H128zm2-4h1.5l2.7 3.9v.1h-1.5l-2.7-4zm9.3 4.1c-.6 0-1.2-.1-1.8-.3-.5-.2-1-.5-1.4-1-.4-.4-.7-.9-.9-1.4-.2-.5-.3-1.1-.3-1.8s.1-1.2.3-1.8c.2-.5.5-1 .9-1.4.4-.4.9-.7 1.4-1 .5-.2 1.1-.3 1.8-.3.6 0 1.2.1 1.8.3.5.2 1 .6 1.4 1 .4.4.7.9.9 1.4.2.5.3 1.1.3 1.8s-.1 1.2-.3 1.8c-.2.5-.5 1-.9 1.4-.4.4-.9.7-1.4 1-.6.2-1.2.3-1.8.3zm0-1.2c.6 0 1.1-.1 1.6-.4.5-.3.8-.7 1.1-1.1.3-.5.4-1.1.4-1.7 0-.6-.1-1.2-.4-1.7-.3-.5-.7-.9-1.1-1.1-.5-.3-1-.4-1.6-.4-.6 0-1.1.1-1.6.4-.5.3-.8.7-1.1 1.1-.3.5-.4 1-.4 1.7 0 .6.1 1.2.4 1.7.3.5.7.9 1.1 1.1.5.3 1 .4 1.6.4zm7.9 1.1-3-8.6h1.4l1.9 5.7.3 1h.1l.3-1 2-5.7h1.4l-3.1 8.6h-1.3zm5.6 0v-8.6h5.2v1.2h-3.9v6.1h3.9v1.2h-5.2zm.7-3.7V163h4.1v1.2h-4.1zm6.2 3.7v-8.6h1.8l2.5 6.5h.1l2.5-6.5h1.8v8.6H167V163l.1-1.5h-.1l-2.5 6.4h-1l-2.5-6.4h-.1l.1 1.5v4.9h-1.3zm10.6 0v-8.6h5.2v1.2h-3.9v6.1h3.9v1.2h-5.2zm.7-3.7V163h4.1v1.2H171zm6.1 3.7v-8.6h1.6l3.9 6.3h.1l-.1-1.7v-4.7h1.3v8.6h-1.4l-4.1-6.6h-.1l.1 1.7v5h-1.3zm10.5 0V160h1.3v7.9h-1.3zm-2.4-7.4v-1.2h6.1v1.2h-6.1z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st4&quot; d=&quot;M200.6 137.1h96v38.4h-96v-38.4z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st2&quot; d=&quot;M232.5 160.9v-8.6h3c.5 0 1 .1 1.4.3.4.2.8.5 1 .9.3.4.4.9.4 1.4 0 .5-.1 1-.4 1.4-.2.4-.6.7-1 .9-.4.2-.9.3-1.4.3h-2.2v-1.2h2.3c.3 0 .6-.1.8-.2.2-.1.4-.3.5-.5.1-.2.2-.4.2-.7 0-.2-.1-.4-.2-.7-.1-.2-.3-.4-.5-.5-.2-.1-.5-.2-.8-.2h-1.7v7.3h-1.4zm11 .1c-.6 0-1.2-.1-1.8-.3-.5-.2-1-.5-1.4-1-.4-.4-.7-.9-.9-1.4-.2-.5-.3-1.1-.3-1.8s.1-1.2.3-1.8c.2-.5.5-1 .9-1.4.4-.4.9-.7 1.4-1 .5-.2 1.1-.3 1.8-.3.6 0 1.2.1 1.8.3.5.2 1 .6 1.4 1 .4.4.7.9.9 1.4.2.5.3 1.1.3 1.8s-.1 1.2-.3 1.8c-.2.5-.5 1-.9 1.4-.4.4-.9.7-1.4 1-.5.2-1.1.3-1.8.3zm0-1.2c.6 0 1.1-.1 1.6-.4.5-.3.8-.7 1.1-1.1.3-.5.4-1.1.4-1.7 0-.6-.1-1.2-.4-1.7-.3-.5-.7-.9-1.1-1.1-.5-.3-1-.4-1.6-.4-.6 0-1.1.1-1.6.4-.5.3-.8.7-1.1 1.1-.3.5-.4 1-.4 1.7 0 .6.1 1.2.4 1.7.3.5.7.9 1.1 1.1.6.3 1.1.4 1.6.4zm9.9 1.2c-.6 0-1.2-.1-1.8-.3-.5-.2-1-.5-1.4-1-.4-.4-.7-.9-.9-1.4-.2-.5-.3-1.1-.3-1.8s.1-1.2.3-1.8c.2-.5.5-1 .9-1.4.4-.4.9-.7 1.4-1 .5-.2 1.1-.3 1.8-.3.6 0 1.2.1 1.8.3.5.2 1 .6 1.4 1 .4.4.7.9.9 1.4.2.5.3 1.1.3 1.8s-.1 1.2-.3 1.8c-.2.5-.5 1-.9 1.4-.4.4-.9.7-1.4 1-.6.2-1.1.3-1.8.3zm0-1.2c.6 0 1.1-.1 1.6-.4.5-.3.8-.7 1.1-1.1.3-.5.4-1.1.4-1.7 0-.6-.1-1.2-.4-1.7-.3-.5-.7-.9-1.1-1.1-.5-.3-1-.4-1.6-.4-.6 0-1.1.1-1.6.4-.5.3-.8.7-1.1 1.1-.3.5-.4 1-.4 1.7 0 .6.1 1.2.4 1.7.3.5.7.9 1.1 1.1.5.3 1 .4 1.6.4zm6 1.1v-8.6h3c.5 0 1 .1 1.4.3.4.2.7.5 1 .9.2.4.4.8.4 1.4 0 .3-.1.7-.2 1s-.3.6-.6.8c-.3.2-.6.4-1 .6-.4.1-.8.2-1.2.2h-2.1v-1.2h2.3c.3 0 .5-.1.7-.2.2-.1.4-.3.5-.5.1-.2.2-.5.2-.7 0-.2-.1-.5-.2-.7-.1-.2-.3-.4-.5-.5-.2-.1-.5-.2-.8-.2h-1.7v7.4h-1.2zm2-4h1.5l2.7 3.9v.1H264l-2.6-4zM178.1 219.9c-.5 0-1-.1-1.5-.3-.5-.2-1-.5-1.4-1-.4-.4-.7-1-.8-1.7l1.5-.6c.1.6.4 1 .8 1.4.4.4.9.5 1.4.5.6 0 1.1-.2 1.5-.6.4-.4.6-.9.6-1.5s-.2-1.1-.6-1.5c-.4-.4-.9-.6-1.5-.6-.4 0-.7.1-1 .2-.3.1-.5.4-.7.6l-1.7-.8.7-5.5h5.9v1.6h-4.5l-.4 2.9h.1c.2-.2.5-.3.8-.5.3-.1.7-.2 1.2-.2.6 0 1.2.2 1.7.5s1 .7 1.3 1.3c.3.5.5 1.2.5 1.9s-.2 1.3-.5 1.9c-.3.6-.8 1-1.3 1.3-.7.5-1.4.7-2.1.7zm9.9 0c-.7 0-1.3-.1-1.9-.4-.5-.3-1-.7-1.4-1.2-.4-.5-.7-1.1-.9-1.8-.2-.7-.3-1.4-.3-2.2 0-.8.1-1.6.3-2.2.2-.7.5-1.3.9-1.8s.9-.9 1.4-1.2c.6-.3 1.2-.5 1.9-.5s1.3.2 1.9.5c.6.3 1 .7 1.4 1.2.4.5.7 1.1.9 1.8.2.7.3 1.4.3 2.2 0 .8-.1 1.6-.3 2.2-.2.7-.5 1.3-.9 1.8s-.9.9-1.4 1.2c-.6.2-1.2.4-1.9.4zm.1-1.7c.6 0 1-.2 1.5-.5.4-.4.7-.9 1-1.5.2-.6.3-1.3.3-2.1s-.1-1.5-.3-2.1c-.2-.6-.5-1.1-1-1.5-.4-.4-.9-.5-1.5-.5s-1.1.2-1.5.5c-.4.4-.7.8-.9 1.5-.2.6-.3 1.3-.3 2.1s.1 1.5.3 2.1c.2.6.5 1.1.9 1.5.4.3.9.5 1.5.5zm10.5 1.7c-.7 0-1.3-.1-1.9-.4-.5-.3-1-.7-1.4-1.2-.4-.5-.7-1.1-.9-1.8-.2-.7-.3-1.4-.3-2.2 0-.8.1-1.6.3-2.2.2-.7.5-1.3.9-1.8s.9-.9 1.4-1.2c.6-.3 1.2-.5 1.9-.5s1.3.2 1.9.5c.6.3 1 .7 1.4 1.2.4.5.7 1.1.9 1.8.2.7.3 1.4.3 2.2 0 .8-.1 1.6-.3 2.2-.2.7-.5 1.3-.9 1.8s-.9.9-1.4 1.2c-.6.2-1.2.4-1.9.4zm0-1.7c.6 0 1-.2 1.5-.5.4-.4.7-.9 1-1.5.2-.6.3-1.3.3-2.1s-.1-1.5-.3-2.1c-.2-.6-.5-1.1-1-1.5-.4-.4-.9-.5-1.5-.5s-1.1.2-1.5.5c-.4.4-.7.8-.9 1.5-.2.6-.3 1.3-.3 2.1s.1 1.5.3 2.1c.2.6.5 1.1.9 1.5.4.3.9.5 1.5.5zm10 1.4v-8.2h1.6v1.1h.1c.2-.3.4-.5.6-.7.3-.2.6-.4.9-.5.3-.1.7-.2 1-.2.6 0 1.1.1 1.5.4.4.3.7.7.9 1.1.3-.4.6-.8 1.1-1.1.5-.3 1-.5 1.7-.5 1 0 1.7.3 2.1.9.5.6.7 1.4.7 2.3v5.2h-1.7v-4.9c0-.7-.1-1.1-.4-1.4-.3-.3-.7-.5-1.2-.5-.4 0-.7.1-1 .3-.3.2-.5.5-.7.9-.2.4-.2.8-.2 1.2v4.4h-1.7v-4.9c0-.7-.1-1.1-.4-1.4-.3-.3-.7-.5-1.2-.5-.4 0-.7.1-1 .3-.3.2-.5.5-.7.9-.2.4-.2.8-.2 1.2v4.4h-1.8zm17.2.3c-.6 0-1.2-.1-1.7-.3-.5-.2-.9-.5-1.2-.8-.3-.3-.5-.7-.7-1.1l1.5-.7c.2.4.5.8.8 1 .4.2.8.3 1.2.3.4 0 .8-.1 1.1-.2.3-.2.5-.4.5-.8 0-.2-.1-.4-.2-.6-.1-.1-.3-.3-.6-.4-.2-.1-.5-.2-.8-.2l-1-.2c-.4-.1-.8-.3-1.1-.5-.3-.2-.6-.5-.8-.8-.2-.3-.3-.7-.3-1.1 0-.5.1-.9.4-1.3.3-.4.7-.6 1.1-.8.5-.2 1-.3 1.6-.3.5 0 1 .1 1.4.2.4.1.8.3 1.1.6.3.3.6.6.8 1l-1.5.7c-.2-.4-.4-.6-.7-.8-.3-.1-.6-.2-1-.2s-.7.1-1 .2c-.3.2-.4.4-.4.6 0 .3.1.5.4.7.2.2.5.3.9.4l1.2.3c.8.2 1.4.5 1.8.9.4.4.6.9.6 1.5 0 .5-.2 1-.5 1.4-.3.4-.7.7-1.2.9-.6.2-1.1.4-1.7.4zM69 220v-1.6s.2-.1.4-.4c.2-.2.5-.5.8-.9l1.1-1.1 1-1c.3-.3.5-.6.7-.8.3-.3.5-.6.7-.8.2-.2.3-.5.4-.7.1-.2.1-.5.1-.8 0-.3-.1-.5-.2-.8-.1-.3-.3-.4-.6-.6-.3-.1-.6-.2-1-.2s-.7.1-1 .2c-.3.1-.5.3-.6.6-.1.2-.3.4-.3.7l-1.5-.6c.1-.3.2-.5.4-.8.2-.3.4-.5.7-.8.3-.3.6-.5 1-.6.4-.2.9-.3 1.4-.2.7 0 1.3.2 1.9.5.5.3.9.7 1.2 1.2.3.5.4 1 .4 1.6 0 .5-.1.9-.2 1.3-.2.4-.4.8-.7 1.2-.3.4-.5.7-.8 1l-.5.5c-.2.2-.4.5-.7.7l-.7.7-.6.6-.4.4h4.8v1.6H69zm13.2.2c-.7 0-1.3-.1-1.9-.4-.5-.3-1-.7-1.4-1.2-.4-.5-.7-1.1-.9-1.8-.2-.7-.3-1.4-.3-2.2 0-.8.1-1.6.3-2.2.2-.7.5-1.3.9-1.8s.9-.9 1.4-1.2c.6-.3 1.2-.5 1.9-.5s1.3.2 1.9.5c.6.3 1 .7 1.4 1.2.4.5.7 1.1.9 1.8.2.7.3 1.4.3 2.2 0 .8-.1 1.6-.3 2.2-.2.7-.5 1.3-.9 1.8s-.9.9-1.4 1.2c-.6.3-1.2.4-1.9.4zm0-1.6c.6 0 1-.2 1.5-.5.4-.4.7-.9 1-1.5.2-.6.3-1.3.3-2.1s-.1-1.5-.3-2.1c-.2-.6-.5-1.1-1-1.5-.4-.4-.9-.5-1.5-.5s-1.1.2-1.5.5c-.4.4-.7.8-.9 1.5-.2.6-.3 1.3-.3 2.1s.1 1.5.3 2.1c.2.6.5 1.1.9 1.5.4.3.9.5 1.5.5zm10.5 1.6c-.7 0-1.3-.1-1.9-.4-.5-.3-1-.7-1.4-1.2-.4-.5-.7-1.1-.9-1.8-.2-.7-.3-1.4-.3-2.2 0-.8.1-1.6.3-2.2.2-.7.5-1.3.9-1.8s.9-.9 1.4-1.2c.6-.3 1.2-.5 1.9-.5s1.3.2 1.9.5c.6.3 1 .7 1.4 1.2.4.5.7 1.1.9 1.8.2.7.3 1.4.3 2.2 0 .8-.1 1.6-.3 2.2-.2.7-.5 1.3-.9 1.8s-.9.9-1.4 1.2c-.6.3-1.2.4-1.9.4zm0-1.6c.6 0 1-.2 1.5-.5.4-.4.7-.9 1-1.5.2-.6.3-1.3.3-2.1s-.1-1.5-.3-2.1c-.2-.6-.5-1.1-1-1.5-.4-.4-.9-.5-1.5-.5s-1.1.2-1.5.5c-.4.4-.7.8-.9 1.5-.2.6-.3 1.3-.3 2.1s.1 1.5.3 2.1c.2.6.5 1.1.9 1.5.5.3 1 .5 1.5.5zm10 1.4v-8.2h1.6v1.1h.1c.2-.3.4-.5.6-.7.3-.2.6-.4.9-.5.3-.1.7-.2 1-.2.6 0 1.1.1 1.5.4.4.3.7.7.9 1.1.3-.4.6-.8 1.1-1.1.5-.3 1-.5 1.7-.5 1 0 1.7.3 2.1.9.5.6.7 1.4.7 2.3v5.2h-1.7v-4.9c0-.7-.1-1.1-.4-1.4-.3-.3-.7-.5-1.2-.5-.4 0-.7.1-1 .3-.3.2-.5.5-.7.9-.2.4-.2.8-.2 1.2v4.4H108v-4.9c0-.7-.1-1.1-.4-1.4-.3-.3-.7-.5-1.2-.5-.4 0-.7.1-1 .3-.3.2-.5.5-.7.9-.2.4-.2.8-.2 1.2v4.4h-1.8zm17.2.2c-.6 0-1.2-.1-1.7-.3-.5-.2-.9-.5-1.2-.8-.3-.3-.5-.7-.7-1.1l1.5-.7c.2.4.5.8.8 1 .4.2.8.3 1.2.3.4 0 .8-.1 1.1-.2.3-.2.5-.4.5-.8 0-.2-.1-.4-.2-.6-.1-.1-.3-.3-.6-.4-.2-.1-.5-.2-.8-.2l-1-.2c-.4-.1-.8-.3-1.1-.5-.3-.2-.6-.5-.8-.8-.2-.3-.3-.7-.3-1.1 0-.5.1-.9.4-1.3.3-.4.7-.6 1.1-.8.5-.2 1-.3 1.6-.3.5 0 1 .1 1.4.2.4.1.8.3 1.1.6.3.3.6.6.8 1l-1.5.7c-.2-.4-.4-.6-.7-.8-.3-.1-.6-.2-1-.2s-.7.1-1 .2c-.3.2-.4.4-.4.6 0 .3.1.5.4.7.2.2.5.3.9.4l1.2.3c.8.2 1.4.5 1.8.9.4.4.6.9.6 1.5 0 .5-.2 1-.5 1.4-.3.4-.7.7-1.2.9-.5.3-1.1.4-1.7.4zM42.4 96.3V82h1.7v14.3h-1.7zm4.6 0V86.1h1.6v1.5h.1c.3-.5.7-.9 1.3-1.3.6-.4 1.3-.5 2-.5 1.2 0 2.2.4 2.8 1.1.6.7 1 1.7 1 2.9v6.5h-1.7V90c0-1-.2-1.7-.7-2.1-.5-.4-1.1-.6-1.8-.6-.6 0-1.1.2-1.5.5-.4.3-.8.7-1 1.2-.2.5-.4 1-.4 1.6v5.7H47zm10-10.2h6v1.5h-6v-1.5zm1.8 7.5V83.3h1.7v10c0 .5.1.9.3 1.2s.6.4 1.1.4c.2 0 .4 0 .6-.1.2-.1.4-.2.5-.2v1.7c-.2.1-.4.1-.6.2-.2.1-.5.1-.8.1-.9 0-1.5-.2-2.1-.8-.5-.6-.7-1.3-.7-2.2zm10.3 3c-1 0-1.9-.2-2.6-.7-.8-.5-1.4-1.1-1.8-1.9-.4-.8-.6-1.7-.6-2.8 0-1 .2-1.9.6-2.7s1-1.5 1.7-2 1.6-.8 2.6-.8 1.9.2 2.6.7c.7.4 1.3 1.1 1.7 1.8.4.8.6 1.7.6 2.7v.5H65V90h7c0-.3-.1-.6-.2-.9-.1-.3-.3-.6-.5-.9-.2-.3-.6-.5-.9-.7-.4-.2-.8-.3-1.4-.3-.7 0-1.2.2-1.7.5s-.8.8-1.1 1.4c-.3.6-.4 1.2-.4 2 0 .9.2 1.6.5 2.2.3.6.8 1 1.3 1.3.5.3 1.1.4 1.7.4.8 0 1.4-.2 1.8-.5.5-.4.9-.8 1.2-1.3l1.4.7c-.4.8-1 1.4-1.7 1.9-.9.6-1.8.8-2.9.8zm6.7-.3V86.1h1.6v1.6h.1c.1-.4.4-.7.7-1 .3-.3.7-.5 1.1-.7s.8-.2 1.2-.2h.7c.2 0 .3.1.5.2v1.8c-.2-.1-.5-.2-.7-.2-.2-.1-.5-.1-.7-.1-.5 0-1 .1-1.4.4-.4.3-.7.7-1 1.1-.2.5-.4 1-.4 1.5v5.7h-1.7zm10.4.3c-.8 0-1.4-.1-2-.4s-1-.7-1.3-1.2c-.3-.5-.5-1.1-.5-1.8 0-.8.2-1.4.6-1.9.4-.5.9-.9 1.5-1.2.7-.3 1.4-.4 2.2-.4.4 0 .9 0 1.2.1s.7.2 1 .3c.3.1.5.2.7.3v-.6c0-.8-.3-1.4-.8-1.8-.5-.5-1.2-.7-2-.7-.6 0-1.1.1-1.6.4-.5.2-.9.6-1.1 1l-1.3-1c.3-.4.6-.7 1-1 .4-.3.9-.5 1.4-.7.5-.2 1.1-.2 1.6-.2 1.4 0 2.5.4 3.2 1.1.8.7 1.2 1.7 1.2 3v6.5h-1.6v-1.5h-.1c-.2.3-.4.6-.7.8s-.7.5-1.1.7c-.5.2-1 .2-1.5.2zm.1-1.5c.6 0 1.1-.1 1.6-.4.5-.3.9-.7 1.2-1.2.3-.5.5-1 .5-1.6-.3-.2-.7-.4-1.2-.5s-1-.2-1.5-.2c-1 0-1.7.2-2.1.6-.4.4-.7.9-.7 1.5s.2 1 .6 1.4 1 .4 1.6.4zm11.8 1.5c-1 0-1.9-.2-2.7-.7-.8-.5-1.4-1.1-1.8-1.9-.4-.8-.7-1.7-.7-2.8 0-1 .2-2 .7-2.8.4-.8 1.1-1.5 1.8-1.9.8-.5 1.7-.7 2.7-.7 1.1 0 2.1.3 2.8.8.7.5 1.3 1.2 1.6 2l-1.5.6c-.2-.6-.6-1.1-1.1-1.4-.5-.3-1.1-.5-1.8-.5-.6 0-1.2.2-1.7.5s-.9.8-1.2 1.4c-.3.6-.5 1.3-.5 2 0 .8.2 1.4.5 2 .3.6.7 1 1.2 1.4.5.3 1.1.5 1.7.5.7 0 1.3-.2 1.9-.5s.9-.8 1.2-1.4l1.5.6c-.3.8-.9 1.5-1.6 2s-1.9.8-3 .8zm5.5-10.5h6v1.5h-6v-1.5zm1.7 7.5V83.3h1.7v10c0 .5.1.9.3 1.2s.6.4 1.1.4c.2 0 .4 0 .6-.1.2-.1.4-.2.5-.2v1.7c-.2.1-.4.1-.6.2-.2.1-.5.1-.8.1-.9 0-1.5-.2-2.1-.8s-.7-1.3-.7-2.2zm6.5 2.7V86.1h1.7v10.2h-1.7zm.9-12c-.3 0-.6-.1-.9-.4s-.4-.5-.4-.9c0-.3.1-.6.4-.9.2-.2.5-.4.9-.4.3 0 .6.1.9.4.2.2.4.5.4.9 0 .3-.1.6-.4.9-.3.2-.6.4-.9.4zm8.1 12.3c-1 0-1.9-.2-2.7-.7-.8-.5-1.4-1.1-1.8-1.9-.4-.8-.7-1.7-.7-2.8 0-1 .2-1.9.7-2.8.4-.8 1.1-1.5 1.8-2 .8-.5 1.7-.7 2.7-.7 1 0 1.9.2 2.7.7.8.5 1.4 1.1 1.9 2s.7 1.7.7 2.7c0 1-.2 1.9-.7 2.8-.4.8-1.1 1.5-1.9 1.9-.8.6-1.7.8-2.7.8zm0-1.5c.6 0 1.2-.2 1.7-.5s1-.8 1.3-1.3c.3-.6.5-1.3.5-2.1s-.2-1.5-.5-2.1-.8-1-1.3-1.3c-.5-.3-1.1-.5-1.7-.5-.6 0-1.2.2-1.7.5s-1 .7-1.3 1.3-.5 1.3-.5 2.1.2 1.5.5 2.1c.3.6.8 1 1.3 1.3.5.4 1.1.5 1.7.5zm7 1.2V86.1h1.6v1.5h.1c.3-.5.7-.9 1.3-1.3.6-.4 1.3-.5 2-.5 1.2 0 2.2.4 2.8 1.1.6.7 1 1.7 1 2.9v6.5h-1.7V90c0-1-.2-1.7-.7-2.1-.5-.4-1.1-.6-1.8-.6-.6 0-1.1.2-1.5.5-.4.3-.8.7-1 1.2-.2.5-.4 1-.4 1.6v5.7h-1.7zM143 86.1h6v1.5h-6v-1.5zm1.7 7.5V83.3h1.7v10c0 .5.1.9.3 1.2s.6.4 1.1.4c.2 0 .4 0 .6-.1.2-.1.4-.2.5-.2v1.7c-.2.1-.4.1-.6.2-.2.1-.5.1-.8.1-.9 0-1.5-.2-2.1-.8-.4-.6-.7-1.3-.7-2.2zm10.5 3c-1 0-1.9-.2-2.7-.7-.8-.5-1.4-1.1-1.8-1.9-.4-.8-.7-1.7-.7-2.8 0-1 .2-1.9.7-2.8.4-.8 1.1-1.5 1.8-2 .8-.5 1.7-.7 2.7-.7 1 0 1.9.2 2.7.7.8.5 1.4 1.1 1.9 2 .4.8.7 1.7.7 2.7 0 1-.2 1.9-.7 2.8-.4.8-1.1 1.5-1.9 1.9-.8.6-1.7.8-2.7.8zm0-1.5c.6 0 1.2-.2 1.7-.5s1-.8 1.3-1.3c.3-.6.5-1.3.5-2.1s-.2-1.5-.5-2.1c-.3-.6-.8-1-1.3-1.3-.5-.3-1.1-.5-1.7-.5-.6 0-1.2.2-1.7.5s-1 .7-1.3 1.3c-.3.6-.5 1.3-.5 2.1s.2 1.5.5 2.1c.3.6.8 1 1.3 1.3.5.4 1.1.5 1.7.5zm12.2 1.2V82h2.1l7.2 11.4h.1l-.1-2.8V82h1.7v14.3h-1.8l-7.5-11.9h-.1l.1 2.8v9.2h-1.7zm18.2.3c-1 0-1.9-.2-2.6-.7-.8-.5-1.4-1.1-1.8-1.9-.4-.8-.6-1.7-.6-2.8 0-1 .2-1.9.6-2.7.4-.8 1-1.5 1.7-2s1.6-.8 2.6-.8 1.9.2 2.6.7c.7.4 1.3 1.1 1.7 1.8.4.8.6 1.7.6 2.7v.5h-8.8V90h7c0-.3-.1-.6-.2-.9-.1-.3-.3-.6-.5-.9-.2-.3-.6-.5-.9-.7-.4-.2-.8-.3-1.4-.3-.7 0-1.2.2-1.7.5s-.8.8-1.1 1.4c-.3.6-.4 1.2-.4 2 0 .9.2 1.6.5 2.2.3.6.8 1 1.3 1.3.5.3 1.1.4 1.7.4.8 0 1.4-.2 1.8-.5.5-.4.9-.8 1.2-1.3l1.4.7c-.4.8-1 1.4-1.7 1.9-1 .6-1.9.8-3 .8zm5.5-.3 4.1-5.9h.2l2.9-4.3h2l-4 5.6h-.1l-3.1 4.6h-2zm.1-10.2h1.9l3.2 4.5h.1l4 5.7h-2l-3-4.5h-.1l-4.1-5.7zm9.9 0h6v1.5h-6v-1.5zm1.8 7.5V83.3h1.7v10c0 .5.1.9.3 1.2.2.3.6.4 1.1.4.2 0 .4 0 .6-.1.2-.1.4-.2.5-.2v1.7c-.2.1-.4.1-.6.2-.2.1-.5.1-.8.1-.9 0-1.5-.2-2.1-.8-.4-.6-.7-1.3-.7-2.2zm11.2 2.7V82h4.8c.8 0 1.5.2 2.2.5.7.4 1.2.8 1.6 1.5.4.6.6 1.4.6 2.2 0 .8-.2 1.6-.6 2.2-.4.6-.9 1.1-1.6 1.5-.7.4-1.4.5-2.2.5H215v-1.6h4c.6 0 1-.1 1.4-.4.4-.3.7-.6.9-1 .2-.4.3-.8.3-1.2s-.1-.8-.3-1.2c-.2-.4-.5-.7-.9-1-.4-.3-.9-.4-1.4-.4h-3.2v12.7h-1.7zm14.1.3c-.8 0-1.4-.1-2-.4s-1-.7-1.3-1.2-.5-1.1-.5-1.8c0-.8.2-1.4.6-1.9s.9-.9 1.5-1.2c.7-.3 1.4-.4 2.2-.4.4 0 .9 0 1.2.1s.7.2 1 .3c.3.1.5.2.7.3v-.6c0-.8-.3-1.4-.8-1.8-.5-.5-1.2-.7-2-.7-.6 0-1.1.1-1.6.4-.5.2-.9.6-1.1 1l-1.3-1c.3-.4.6-.7 1-1 .4-.3.9-.5 1.4-.7s1.1-.2 1.6-.2c1.4 0 2.5.4 3.2 1.1.8.7 1.2 1.7 1.2 3v6.5h-1.6v-1.5h-.1c-.2.3-.4.6-.7.8s-.7.5-1.1.7c-.6.2-1 .2-1.5.2zm.1-1.5c.6 0 1.1-.1 1.6-.4.5-.3.9-.7 1.2-1.2.3-.5.5-1 .5-1.6-.3-.2-.7-.4-1.2-.5s-1-.2-1.5-.2c-1 0-1.7.2-2.1.6-.4.4-.7.9-.7 1.5s.2 1 .6 1.4 1 .4 1.6.4zm7.4 1.2V86.1h1.7v10.2h-1.7zm.8-12c-.3 0-.6-.1-.9-.4s-.4-.5-.4-.9c0-.3.1-.6.4-.9.2-.2.5-.4.9-.4.3 0 .6.1.9.4.2.2.4.5.4.9 0 .3-.1.6-.4.9-.2.2-.5.4-.9.4zm3.5 12V86.1h1.6v1.5h.1c.3-.5.7-.9 1.3-1.3.6-.4 1.3-.5 2-.5 1.2 0 2.2.4 2.8 1.1.6.7 1 1.7 1 2.9v6.5h-1.7V90c0-1-.2-1.7-.7-2.1-.5-.4-1.1-.6-1.8-.6-.6 0-1.1.2-1.5.5-.4.3-.8.7-1 1.2-.2.5-.4 1-.4 1.6v5.7H240zm10.1-10.2h6v1.5h-6v-1.5zm1.7 7.5V83.3h1.7v10c0 .5.1.9.3 1.2s.6.4 1.1.4c.2 0 .4 0 .6-.1.2-.1.4-.2.5-.2v1.7c-.2.1-.4.1-.6.2-.2.1-.5.1-.8.1-.9 0-1.5-.2-2.1-.8s-.7-1.3-.7-2.2zM96 172.8c-2.4 0-4.3 1.9-4.3 4.3 0 2 1.4 3.7 3.3 4.2v20.1h2v-20.1c1.9-.5 3.3-2.1 3.3-4.2 0-2.3-1.9-4.3-4.3-4.3zM201.6 172.6c-2.4 0-4.3 1.9-4.3 4.3 0 2 1.4 3.7 3.3 4.2v19.8h2v-19.8c1.9-.5 3.3-2.1 3.3-4.2 0-2.4-1.9-4.3-4.3-4.3z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;
  &lt;svg title=&quot;A diagram of the INP thresholds. An INP at or below 200 milliseconds is considered good. Between 200 and 500 milliseconds suggests a page&#39;s responsiveness needs improvement. Anything over 500 milliseconds means that a page&#39;s responsiveness is poor.&quot; class=&quot;inp-desktop&quot; version=&quot;1.1&quot; id=&quot;Layer_1&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; x=&quot;0&quot; y=&quot;0&quot; viewBox=&quot;0 0 658.4 113.6&quot; style=&quot;enable-background:new 0 0 658.4 113.6&quot; xml:space=&quot;preserve&quot;&gt;&lt;style&gt;.st0{fill: #2979FF} v.st1{fill-rule: evenodd;clip-rule: evenodd;fill: #0CCE6B} .st2 {fill: #191919} .st3{fill-rule: evenodd;clip-rule: evenodd;fill: #FFA400} .st4{fill-rule: evenodd;clip-rule: evenodd;fill: #FF4E42} @media screen and (prefers-color-scheme: light){.st2{fill: #191919}} @media screen and (prefers-color-scheme: dark){.st2{fill: #fff}} [data-user-theme=dark] .st2{fill: #fff}&lt;/style&gt;&lt;path class=&quot;st0&quot; d=&quot;M30.2 68.7V0h13v68.7h-13zm28.7 0V0H74l27.7 46.1h.8l-.8-13.2V0h12.9v68.7H101L71.7 20.1h-.8l.8 13.2v35.4H58.9zm71.3 0V0h24.2c4.4 0 8.4.9 11.9 2.7 3.5 1.8 6.3 4.4 8.4 7.6 2.1 3.3 3.1 7 3.1 11.3 0 4.3-1 8.1-3.1 11.4-2.1 3.3-4.9 5.8-8.4 7.7-3.5 1.8-7.5 2.7-11.9 2.7h-17V31.2h17.4c2.2 0 4.1-.4 5.7-1.3 1.5-.9 2.7-2.1 3.5-3.5.8-1.4 1.2-3 1.2-4.7 0-1.7-.4-3.2-1.2-4.6-.8-1.4-1.9-2.6-3.5-3.5-1.5-.9-3.4-1.3-5.7-1.3h-11.6v56.5h-13z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st1&quot; d=&quot;M303.2 14.9h115.2v43.2H303.2V14.9z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st2&quot; d=&quot;M345.3 41.5c-.7 0-1.3-.1-1.9-.4-.6-.2-1.1-.6-1.6-1s-.8-1-1.1-1.6c-.3-.6-.4-1.3-.4-2s.1-1.4.4-2c.3-.6.6-1.1 1.1-1.6.5-.5 1-.8 1.6-1 .6-.2 1.2-.4 1.9-.4s1.4.1 2 .4c.6.2 1.1.6 1.6 1.1l-1 1c-.2-.2-.4-.4-.7-.6-.3-.2-.5-.3-.9-.4-.3-.1-.6-.1-1-.1-.5 0-.9.1-1.3.3-.4.2-.8.4-1.1.7-.3.3-.6.7-.8 1.1-.2.4-.3.9-.3 1.5s.1 1 .3 1.5c.2.4.5.8.8 1.1.3.3.7.6 1.1.7.4.2.9.2 1.3.2s.8-.1 1.2-.2c.4-.1.7-.3 1-.5.3-.2.5-.5.7-.8.2-.3.3-.7.3-1.1h-3.3v-1.3h4.6v.8c0 .7-.1 1.3-.4 1.9-.2.6-.6 1-1 1.5-.4.4-.9.7-1.5.9-.3.2-.9.3-1.6.3zm10.5 0c-.7 0-1.4-.1-2-.4-.6-.3-1.1-.6-1.6-1.1-.4-.5-.8-1-1-1.6-.2-.6-.4-1.2-.4-1.9s.1-1.3.4-1.9c.2-.6.6-1.1 1-1.6s1-.8 1.6-1.1c.6-.3 1.3-.4 2-.4s1.4.1 2 .4c.6.2 1.1.6 1.6 1.1.5.5.8 1 1 1.6.2.6.4 1.2.4 1.9s-.1 1.3-.4 1.9c-.2.6-.6 1.1-1 1.6s-1 .8-1.6 1.1-1.2.4-2 .4zm0-1.4c.6 0 1.2-.2 1.8-.5.5-.3.9-.7 1.2-1.3s.5-1.2.5-1.9-.2-1.3-.5-1.9-.7-1-1.2-1.3c-.5-.3-1.1-.5-1.8-.5-.6 0-1.2.2-1.8.5-.5.3-.9.7-1.2 1.3s-.5 1.2-.5 1.9.2 1.3.5 1.9.7 1 1.2 1.3c.6.4 1.2.5 1.8.5zm11 1.4c-.7 0-1.4-.1-2-.4-.6-.3-1.1-.6-1.6-1.1-.4-.5-.8-1-1-1.6-.2-.6-.4-1.2-.4-1.9s.1-1.3.4-1.9c.2-.6.6-1.1 1-1.6s1-.8 1.6-1.1c.6-.3 1.3-.4 2-.4s1.4.1 2 .4c.6.2 1.1.6 1.6 1.1.5.5.8 1 1 1.6.2.6.4 1.2.4 1.9s-.1 1.3-.4 1.9c-.2.6-.6 1.1-1 1.6s-1 .8-1.6 1.1-1.3.4-2 .4zm0-1.4c.6 0 1.2-.2 1.8-.5.5-.3.9-.7 1.2-1.3s.5-1.2.5-1.9-.2-1.3-.5-1.9-.7-1-1.2-1.3c-.5-.3-1.1-.5-1.8-.5-.6 0-1.2.2-1.8.5-.5.3-.9.7-1.2 1.3s-.5 1.2-.5 1.9.2 1.3.5 1.9.7 1 1.2 1.3c.6.4 1.2.5 1.8.5zm6.6 1.2v-9.5h3c1 0 1.9.2 2.6.6.7.4 1.3 1 1.7 1.7s.6 1.5.6 2.5c0 .9-.2 1.8-.6 2.5s-1 1.3-1.7 1.7c-.7.4-1.6.6-2.6.6h-3zm1.5-1.4h1.5c.7 0 1.3-.1 1.8-.4.5-.3.9-.7 1.2-1.2.3-.5.4-1.1.4-1.8s-.1-1.3-.4-1.8c-.3-.5-.7-.9-1.2-1.2-.5-.3-1.1-.4-1.8-.4h-1.5v6.8z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st3&quot; d=&quot;M418.4 14.9h124.8v43.2H418.4V14.9z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st2&quot; d=&quot;M460.8 33.3v-9.5h1.7l4.3 7h.1l-.1-1.8v-5.2h1.5v9.5h-1.5l-4.5-7.4h-.1l.1 1.8v5.5h-1.5zm9.7 0v-9.5h5.8v1.4H472V32h4.3v1.4h-5.8zm.7-4.1v-1.4h4.6v1.4h-4.6zm6.9 4.1v-9.5h5.8v1.4h-4.3V32h4.3v1.4h-5.8zm.8-4.1v-1.4h4.6v1.4h-4.6zm6.8 4.1v-9.5h3c1 0 1.9.2 2.6.6.7.4 1.3 1 1.7 1.7s.6 1.5.6 2.5c0 .9-.2 1.8-.6 2.5s-1 1.3-1.7 1.7c-.7.4-1.6.6-2.6.6h-3zm1.5-1.4h1.5c.7 0 1.3-.1 1.8-.4.5-.3.9-.7 1.2-1.2.3-.5.4-1.1.4-1.8s-.1-1.3-.4-1.8c-.3-.5-.7-.9-1.2-1.2-.5-.3-1.1-.4-1.8-.4h-1.5v6.8zm10.9 1.6c-.5 0-1-.1-1.5-.3-.5-.2-.9-.5-1.2-.9-.3-.4-.6-.9-.8-1.5l1.4-.6c.1.5.4.9.8 1.3.4.3.8.5 1.3.5.3 0 .6-.1.8-.2.3-.1.5-.3.6-.5.2-.2.2-.5.2-.8 0-.3-.1-.5-.2-.7-.1-.2-.3-.4-.6-.5-.3-.2-.7-.3-1.1-.5l-.6-.2c-.3-.1-.5-.2-.8-.4-.3-.1-.5-.3-.7-.5-.2-.2-.4-.5-.5-.7-.1-.3-.2-.6-.2-1 0-.5.1-.9.4-1.3.2-.4.6-.7 1-.9.4-.2 1-.4 1.6-.4.6 0 1.1.1 1.5.3.4.2.7.5 1 .8.2.3.4.6.5.9l-1.3.6c-.1-.2-.2-.4-.3-.5-.1-.2-.3-.3-.5-.4-.2-.1-.5-.2-.8-.2-.3 0-.5.1-.8.2-.2.1-.4.2-.6.4-.1.2-.2.4-.2.6 0 .3.1.6.4.8s.7.4 1.2.6l.6.2c.3.1.6.2.9.4.3.1.6.3.8.6.2.2.4.5.6.8.1.3.2.7.2 1.2s-.1.9-.3 1.3c-.2.4-.4.6-.8.9-.3.2-.7.4-1 .5-.3 0-.7.1-1 .1zM434.4 49.3v-9.5h1.5v9.5h-1.5zm3.6 0v-9.5h2l2.8 7.3h.1l2.8-7.3h2v9.5h-1.4v-5.4l.1-1.7h-.1l-2.8 7.1h-1.2l-2.8-7.1h-.1l.1 1.7v5.4H438zm11.7 0v-9.5h3.3c.6 0 1.1.1 1.6.4.5.2.9.6 1.1 1 .3.4.4.9.4 1.5s-.1 1.1-.4 1.5c-.3.4-.7.8-1.1 1-.5.2-1 .4-1.6.4h-2.5v-1.4h2.5c.4 0 .7-.1.9-.2.2-.1.4-.3.5-.6.1-.2.2-.5.2-.8 0-.3-.1-.5-.2-.7-.1-.2-.3-.4-.5-.6-.2-.2-.5-.2-.9-.2h-1.8v8.2h-1.5zm8 0v-9.5h3.3c.6 0 1.1.1 1.6.4.5.2.8.6 1.1 1s.4.9.4 1.5c0 .4-.1.8-.2 1.1s-.4.7-.7.9c-.3.3-.6.5-1 .6-.4.1-.9.2-1.4.2h-2.3v-1.3h2.5c.3 0 .6-.1.8-.2.2-.1.4-.3.6-.6.2-.2.2-.5.2-.8 0-.3-.1-.5-.2-.7-.1-.2-.3-.4-.5-.6-.2-.1-.5-.2-.9-.2h-1.9v8.2h-1.4zm2.2-4.4h1.7l3 4.3v.1h-1.7l-3-4.4zm10.3 4.6c-.7 0-1.4-.1-2-.4-.6-.3-1.1-.6-1.6-1.1-.4-.5-.8-1-1-1.6-.2-.6-.4-1.2-.4-1.9s.1-1.3.4-1.9c.2-.6.6-1.1 1-1.6s1-.8 1.6-1.1c.6-.3 1.3-.4 2-.4s1.4.1 2 .4c.6.2 1.1.6 1.6 1.1.5.5.8 1 1 1.6.2.6.4 1.2.4 1.9s-.1 1.3-.4 1.9c-.2.6-.6 1.1-1 1.6s-1 .8-1.6 1.1c-.6.3-1.2.4-2 .4zm0-1.4c.6 0 1.2-.2 1.8-.5.5-.3.9-.7 1.2-1.3.3-.5.5-1.2.5-1.9s-.2-1.3-.5-1.9c-.3-.5-.7-1-1.2-1.3-.5-.3-1.1-.5-1.8-.5s-1.2.2-1.8.5c-.5.3-.9.7-1.2 1.3-.3.5-.5 1.2-.5 1.9s.2 1.3.5 1.9c.3.5.7 1 1.2 1.3.6.4 1.2.5 1.8.5zm8.8 1.2-3.4-9.5h1.6l2.2 6.3.3 1.1h.1l.4-1.1 2.2-6.3h1.6l-3.5 9.5H479zm6.2 0v-9.5h5.8v1.4h-4.3V48h4.3v1.4h-5.8zm.8-4.1v-1.4h4.6v1.4H486zm6.8 4.1v-9.5h2l2.8 7.3h.1l2.8-7.3h2v9.5H501v-5.4l.1-1.7h-.1l-2.8 7.1H497l-2.8-7.1h-.1l.1 1.7v5.4h-1.4zm11.8 0v-9.5h5.8v1.4h-4.3V48h4.3v1.4h-5.8zm.8-4.1v-1.4h4.6v1.4h-4.6zm6.8 4.1v-9.5h1.7l4.3 7h.1l-.1-1.8v-5.2h1.5v9.5h-1.5l-4.5-7.4h-.1l.1 1.8v5.5h-1.5zm11.7 0v-8.8h1.5v8.8h-1.5zm-2.7-8.2v-1.4h6.8v1.4h-6.8z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st4&quot; d=&quot;M543.2 14.9h115.2v43.2H543.2V14.9z&quot;&gt;&lt;/path&gt;&lt;path class=&quot;st2&quot; d=&quot;M582.9 41.3v-9.5h3.3c.6 0 1.1.1 1.6.4.5.2.9.6 1.1 1 .3.4.4.9.4 1.5s-.1 1.1-.4 1.5c-.3.4-.7.8-1.1 1-.5.2-1 .4-1.6.4h-2.5v-1.4h2.5c.4 0 .7-.1.9-.2.2-.1.4-.3.5-.6.1-.2.2-.5.2-.8 0-.3-.1-.5-.2-.7-.1-.2-.3-.4-.5-.6-.2-.2-.5-.2-.9-.2h-1.8v8.2h-1.5zm12.3.2c-.7 0-1.4-.1-2-.4-.6-.3-1.1-.6-1.6-1.1-.4-.5-.8-1-1-1.6-.2-.6-.4-1.2-.4-1.9s.1-1.3.4-1.9c.2-.6.6-1.1 1-1.6s1-.8 1.6-1.1c.6-.3 1.3-.4 2-.4s1.4.1 2 .4c.6.2 1.1.6 1.6 1.1.5.5.8 1 1 1.6.2.6.4 1.2.4 1.9s-.1 1.3-.4 1.9c-.2.6-.6 1.1-1 1.6s-1 .8-1.6 1.1c-.7.3-1.3.4-2 .4zm0-1.4c.6 0 1.2-.2 1.8-.5.5-.3.9-.7 1.2-1.3.3-.5.5-1.2.5-1.9s-.2-1.3-.5-1.9c-.3-.5-.7-1-1.2-1.3-.5-.3-1.1-.5-1.8-.5-.6 0-1.2.2-1.8.5-.5.3-.9.7-1.2 1.3-.3.5-.5 1.2-.5 1.9s.2 1.3.5 1.9c.3.5.7 1 1.2 1.3.6.4 1.1.5 1.8.5zm10.9 1.4c-.7 0-1.4-.1-2-.4-.6-.3-1.1-.6-1.6-1.1-.4-.5-.8-1-1-1.6-.2-.6-.4-1.2-.4-1.9s.1-1.3.4-1.9c.2-.6.6-1.1 1-1.6s1-.8 1.6-1.1c.6-.3 1.3-.4 2-.4s1.4.1 2 .4c.6.2 1.1.6 1.6 1.1.5.5.8 1 1 1.6.2.6.4 1.2.4 1.9s-.1 1.3-.4 1.9c-.2.6-.6 1.1-1 1.6s-1 .8-1.6 1.1c-.6.3-1.3.4-2 .4zm0-1.4c.6 0 1.2-.2 1.8-.5.5-.3.9-.7 1.2-1.3.3-.5.5-1.2.5-1.9s-.2-1.3-.5-1.9c-.3-.5-.7-1-1.2-1.3-.5-.3-1.1-.5-1.8-.5s-1.2.2-1.8.5c-.5.3-.9.7-1.2 1.3-.3.5-.5 1.2-.5 1.9s.2 1.3.5 1.9c.3.5.7 1 1.2 1.3.6.4 1.2.5 1.8.5zm6.7 1.2v-9.5h3.3c.6 0 1.1.1 1.6.4.5.2.8.6 1.1 1s.4.9.4 1.5c0 .4-.1.8-.2 1.1s-.4.7-.7.9c-.3.3-.6.5-1 .6-.4.1-.9.2-1.4.2h-2.3v-1.3h2.5c.3 0 .6-.1.8-.2.2-.1.4-.3.6-.6.2-.2.2-.5.2-.8 0-.3-.1-.5-.2-.7-.1-.2-.3-.4-.5-.6-.2-.1-.5-.2-.9-.2h-1.9v8.2h-1.4zm2.2-4.4h1.7l3 4.3v.1H618l-3-4.4zM517.7 102.9c-.6 0-1.1-.1-1.7-.3-.6-.2-1-.6-1.5-1-.4-.5-.7-1.1-.9-1.8l1.7-.7c.2.6.4 1.1.8 1.5.4.4.9.6 1.6.6.7 0 1.2-.2 1.6-.6.4-.4.7-1 .7-1.6 0-.7-.2-1.2-.6-1.6-.4-.4-1-.7-1.6-.7-.4 0-.8.1-1.1.2-.3.2-.6.4-.8.7l-1.8-.8.7-6h6.4v1.7h-4.9l-.4 3.2h.1c.3-.2.6-.4.9-.5.4-.1.8-.2 1.3-.2.7 0 1.3.2 1.9.5.6.3 1 .8 1.4 1.4.4.6.5 1.3.5 2 0 .8-.2 1.5-.5 2.1-.4.6-.8 1.1-1.5 1.4-.7.3-1.5.5-2.3.5zm10.8 0c-.7 0-1.4-.2-2-.5-.6-.3-1.1-.8-1.5-1.3-.4-.6-.8-1.2-1-2-.2-.8-.3-1.6-.3-2.4 0-.9.1-1.7.3-2.5.2-.8.6-1.4 1-2 .4-.6.9-1 1.5-1.3.6-.3 1.3-.5 2-.5.8 0 1.4.2 2 .5.6.3 1.1.8 1.5 1.3.4.6.8 1.2 1 2 .2.8.3 1.6.3 2.5 0 .9-.1 1.7-.3 2.4-.2.8-.5 1.4-1 2-.4.6-.9 1-1.5 1.3-.6.3-1.2.5-2 .5zm0-1.8c.6 0 1.1-.2 1.6-.6.4-.4.8-.9 1-1.6.2-.7.4-1.4.4-2.2 0-.8-.1-1.6-.4-2.3-.2-.7-.6-1.2-1-1.6-.4-.4-1-.6-1.6-.6-.6 0-1.2.2-1.6.6-.4.4-.8.9-1 1.6-.2.7-.4 1.4-.4 2.3 0 .8.1 1.6.4 2.2.2.7.6 1.2 1 1.6.5.4 1 .6 1.6.6zm11.4 1.8c-.7 0-1.4-.2-2-.5-.6-.3-1.1-.8-1.5-1.3-.4-.6-.8-1.2-1-2-.2-.8-.3-1.6-.3-2.4 0-.9.1-1.7.3-2.5.2-.8.6-1.4 1-2 .4-.6.9-1 1.5-1.3.6-.3 1.3-.5 2-.5.8 0 1.4.2 2 .5.6.3 1.1.8 1.5 1.3.4.6.8 1.2 1 2 .2.8.3 1.6.3 2.5 0 .9-.1 1.7-.3 2.4-.2.8-.5 1.4-1 2-.4.6-.9 1-1.5 1.3-.5.3-1.2.5-2 .5zm.1-1.8c.6 0 1.1-.2 1.6-.6.4-.4.8-.9 1-1.6.2-.7.4-1.4.4-2.2 0-.8-.1-1.6-.4-2.3-.2-.7-.6-1.2-1-1.6-.4-.4-1-.6-1.6-.6-.6 0-1.2.2-1.6.6-.4.4-.8.9-1 1.6-.2.7-.4 1.4-.4 2.3 0 .8.1 1.6.4 2.2.2.7.6 1.2 1 1.6.4.4.9.6 1.6.6zm10.8 1.5v-8.8h1.8V95h.1c.2-.3.4-.5.7-.8s.6-.4.9-.5c.4-.1.7-.2 1.1-.2.7 0 1.2.2 1.7.5s.8.7 1 1.2c.3-.5.7-.8 1.2-1.2.5-.3 1.1-.5 1.8-.5 1 0 1.8.3 2.3 1 .5.6.8 1.5.8 2.5v5.7h-1.9v-5.4c0-.7-.2-1.2-.5-1.5-.3-.3-.7-.5-1.3-.5-.4 0-.8.1-1.1.4-.3.2-.6.6-.8 1s-.3.9-.3 1.3v4.7h-1.9v-5.4c0-.7-.2-1.2-.5-1.5-.3-.3-.8-.5-1.3-.5-.4 0-.8.1-1.1.4-.3.2-.6.6-.7 1s-.3.9-.3 1.3v4.7h-1.7zm18.6.3c-.7 0-1.3-.1-1.8-.3-.5-.2-.9-.5-1.3-.9-.3-.4-.6-.8-.7-1.2l1.7-.7c.2.5.5.8.9 1.1.4.2.8.4 1.3.4s.9-.1 1.2-.2c.3-.2.5-.4.5-.8 0-.2-.1-.5-.2-.6s-.4-.3-.6-.4l-.9-.3-1.1-.2c-.4-.1-.8-.3-1.2-.5-.4-.2-.7-.5-.9-.9-.2-.4-.3-.8-.3-1.2 0-.5.2-1 .5-1.4.3-.4.7-.7 1.2-.9.5-.2 1.1-.3 1.7-.3.6 0 1.1.1 1.5.2.5.1.9.3 1.2.6.3.3.6.6.8 1l-1.6.7c-.2-.4-.5-.7-.8-.8-.3-.2-.7-.2-1.1-.2-.4 0-.8.1-1.1.3-.3.2-.4.4-.4.7 0 .3.1.5.4.7.3.2.6.3 1 .4l1.3.3c.9.2 1.5.6 2 1 .4.4.7 1 .7 1.6 0 .6-.2 1.1-.5 1.5-.3.4-.8.7-1.3 1-.8.2-1.5.3-2.1.3zM389.2 102.6v-1.8l.4-.4.9-.9c.4-.4.7-.8 1.1-1.2l1.1-1.1.8-.8c.3-.3.6-.6.8-.9.2-.3.3-.5.4-.8.1-.2.1-.5.1-.8 0-.3-.1-.6-.2-.8-.1-.3-.4-.5-.7-.6-.3-.2-.6-.2-1.1-.2-.4 0-.7.1-1 .2-.3.2-.5.4-.7.6-.2.2-.3.5-.3.7l-1.7-.7c.1-.3.2-.6.4-.9.2-.3.4-.6.8-.9.3-.3.7-.5 1.1-.7.4-.2.9-.3 1.5-.3.8 0 1.4.2 2 .5.6.3 1 .7 1.3 1.2.3.5.5 1.1.5 1.7 0 .5-.1 1-.3 1.5-.2.5-.4.9-.7 1.3-.3.4-.6.7-.9 1-.2.1-.4.3-.6.6-.2.2-.5.5-.7.8l-.8.8-.7.7-.4.4h5.2v1.7h-7.6zm14.2.3c-.7 0-1.4-.2-2-.5-.6-.3-1.1-.8-1.5-1.3-.4-.6-.8-1.2-1-2-.2-.8-.3-1.6-.3-2.4 0-.9.1-1.7.3-2.5.2-.8.6-1.4 1-2 .4-.6.9-1 1.5-1.3.6-.3 1.3-.5 2-.5.8 0 1.4.2 2 .5.6.3 1.1.8 1.5 1.3.4.6.8 1.2 1 2 .2.8.3 1.6.3 2.5 0 .9-.1 1.7-.3 2.4-.2.8-.5 1.4-1 2-.4.6-.9 1-1.5 1.3-.5.3-1.2.5-2 .5zm0-1.8c.6 0 1.1-.2 1.6-.6.4-.4.8-.9 1-1.6.2-.7.4-1.4.4-2.2 0-.8-.1-1.6-.4-2.3-.2-.7-.6-1.2-1-1.6-.4-.4-1-.6-1.6-.6-.6 0-1.2.2-1.6.6-.4.4-.8.9-1 1.6-.2.7-.4 1.4-.4 2.3 0 .8.1 1.6.4 2.2.2.7.6 1.2 1 1.6.5.4 1 .6 1.6.6zm11.5 1.8c-.7 0-1.4-.2-2-.5-.6-.3-1.1-.8-1.5-1.3-.4-.6-.8-1.2-1-2-.2-.8-.3-1.6-.3-2.4 0-.9.1-1.7.3-2.5.2-.8.6-1.4 1-2 .4-.6.9-1 1.5-1.3.6-.3 1.3-.5 2-.5.8 0 1.4.2 2 .5.6.3 1.1.8 1.5 1.3.4.6.8 1.2 1 2 .2.8.3 1.6.3 2.5 0 .9-.1 1.7-.3 2.4-.2.8-.5 1.4-1 2-.4.6-.9 1-1.5 1.3-.6.3-1.3.5-2 .5zm0-1.8c.6 0 1.1-.2 1.6-.6.4-.4.8-.9 1-1.6.2-.7.4-1.4.4-2.2 0-.8-.1-1.6-.4-2.3-.2-.7-.6-1.2-1-1.6-.4-.4-1-.6-1.6-.6-.6 0-1.2.2-1.6.6-.4.4-.8.9-1 1.6-.2.7-.4 1.4-.4 2.3 0 .8.1 1.6.4 2.2.2.7.6 1.2 1 1.6.4.4.9.6 1.6.6zm10.8 1.5v-8.8h1.8V95h.1c.2-.3.4-.5.7-.8s.6-.4.9-.5c.4-.1.7-.2 1.1-.2.7 0 1.2.2 1.7.5s.8.7 1 1.2c.3-.5.7-.8 1.2-1.2.5-.3 1.1-.5 1.8-.5 1 0 1.8.3 2.3 1 .5.6.8 1.5.8 2.5v5.7h-1.9v-5.4c0-.7-.2-1.2-.5-1.5-.3-.3-.7-.5-1.3-.5-.4 0-.8.1-1.1.4-.3.2-.6.6-.8 1s-.3.9-.3 1.3v4.7h-1.9v-5.4c0-.7-.2-1.2-.5-1.5-.3-.3-.8-.5-1.3-.5-.4 0-.8.1-1.1.4-.3.2-.6.6-.7 1s-.3.9-.3 1.3v4.7h-1.7zm18.6.3c-.7 0-1.3-.1-1.8-.3-.5-.2-.9-.5-1.3-.9-.3-.4-.6-.8-.7-1.2l1.7-.7c.2.5.5.8.9 1.1.4.2.8.4 1.3.4s.9-.1 1.2-.2c.3-.2.5-.4.5-.8 0-.2-.1-.5-.2-.6-.2-.2-.4-.3-.6-.4l-.9-.3-1.1-.2c-.4-.1-.8-.3-1.2-.5-.4-.2-.7-.5-.9-.9-.2-.4-.3-.8-.3-1.2 0-.5.2-1 .5-1.4.3-.4.7-.7 1.2-.9.5-.2 1.1-.3 1.7-.3.6 0 1.1.1 1.5.2.5.1.9.3 1.2.6.3.3.6.6.8 1l-1.6.7c-.2-.4-.5-.7-.8-.8-.3-.2-.7-.2-1.1-.2-.4 0-.8.1-1.1.3-.3.2-.4.4-.4.7 0 .3.1.5.4.7.3.2.6.3 1 .4l1.3.3c.9.2 1.5.6 2 1 .4.4.7 1 .7 1.6 0 .6-.2 1.1-.5 1.5-.3.4-.8.7-1.3 1-.8.2-1.4.3-2.1.3zM0 113.3V99h1.7v14.3H0zm4.6 0v-10.2h1.6v1.5h.1c.3-.5.7-.9 1.3-1.3.6-.4 1.3-.5 2-.5 1.2 0 2.2.4 2.8 1.1.6.7 1 1.7 1 2.9v6.5h-1.7V107c0-1-.2-1.7-.7-2.1-.5-.4-1.1-.6-1.8-.6-.6 0-1.1.2-1.5.5-.4.3-.8.7-1 1.2-.2.5-.4 1-.4 1.6v5.7H4.6zm10-10.2h6v1.5h-6v-1.5zm1.8 7.5v-10.4h1.7v10c0 .5.1.9.3 1.2.2.3.6.4 1.1.4.2 0 .4 0 .6-.1.2-.1.4-.2.5-.2v1.7c-.2.1-.4.1-.6.2-.2.1-.5.1-.8.1-.9 0-1.5-.2-2.1-.8-.5-.5-.7-1.2-.7-2.1zm10.3 3c-1 0-1.9-.2-2.6-.7-.8-.5-1.4-1.1-1.8-1.9-.4-.8-.6-1.7-.6-2.8 0-1 .2-1.9.6-2.7.4-.8 1-1.5 1.7-2s1.6-.8 2.6-.8 1.9.2 2.6.7c.7.4 1.3 1.1 1.7 1.8.4.8.6 1.7.6 2.7v.5h-8.8V107h7c0-.3-.1-.6-.2-.9-.1-.3-.3-.6-.5-.9-.2-.3-.6-.5-.9-.7-.4-.2-.8-.3-1.4-.3-.7 0-1.2.2-1.7.5s-.8.8-1.1 1.4c-.3.6-.4 1.2-.4 2 0 .9.2 1.6.5 2.2.3.6.8 1 1.3 1.3.5.3 1.1.4 1.7.4.8 0 1.4-.2 1.8-.5.5-.4.9-.8 1.2-1.3l1.4.7c-.4.8-1 1.4-1.7 1.9-1 .5-1.9.8-3 .8zm6.7-.3v-10.2H35v1.6h.1c.1-.4.4-.7.7-1 .3-.3.7-.5 1.1-.7.4-.2.8-.2 1.2-.2h.7c.2 0 .3.1.5.2v1.8c-.2-.1-.5-.2-.7-.2-.2-.1-.5-.1-.7-.1-.5 0-1 .1-1.4.4-.4.3-.7.7-1 1.1-.2.5-.4 1-.4 1.5v5.7h-1.7zm10.4.3c-.8 0-1.4-.1-2-.4-.6-.3-1-.7-1.3-1.2-.3-.5-.5-1.1-.5-1.8 0-.8.2-1.4.6-1.9.4-.5.9-.9 1.5-1.2.7-.3 1.4-.4 2.2-.4.4 0 .9 0 1.2.1.4.1.7.2 1 .3.3.1.5.2.7.3v-.6c0-.8-.3-1.4-.8-1.8-.5-.5-1.2-.7-2-.7-.6 0-1.1.1-1.6.4-.5.2-.9.6-1.1 1l-1.3-1c.3-.4.6-.7 1-1 .4-.3.9-.5 1.4-.7.5-.2 1.1-.2 1.6-.2 1.4 0 2.5.4 3.2 1.1.8.7 1.2 1.7 1.2 3v6.5h-1.6v-1.5h-.1c-.2.3-.4.6-.7.8-.3.3-.7.5-1.1.7-.5.1-1 .2-1.5.2zm.1-1.5c.6 0 1.1-.1 1.6-.4.5-.3.9-.7 1.2-1.2.3-.5.5-1 .5-1.6-.3-.2-.7-.4-1.2-.5-.5-.1-1-.2-1.5-.2-1 0-1.7.2-2.1.6-.4.4-.7.9-.7 1.5s.2 1 .6 1.4c.4.2 1 .4 1.6.4zm11.7 1.5c-1 0-1.9-.2-2.7-.7-.8-.5-1.4-1.1-1.8-1.9-.4-.8-.7-1.7-.7-2.8 0-1 .2-2 .7-2.8.4-.8 1.1-1.5 1.8-1.9.8-.5 1.7-.7 2.7-.7 1.1 0 2.1.3 2.8.8.7.5 1.3 1.2 1.6 2l-1.5.6c-.2-.6-.6-1.1-1.1-1.4-.5-.3-1.1-.5-1.8-.5-.6 0-1.2.2-1.7.5s-.9.8-1.2 1.4c-.3.6-.5 1.3-.5 2 0 .8.2 1.4.5 2 .3.6.7 1 1.2 1.4.5.3 1.1.5 1.7.5.7 0 1.3-.2 1.9-.5.5-.3.9-.8 1.2-1.4l1.5.6c-.3.8-.9 1.5-1.6 2-.9.5-1.8.8-3 .8zm5.5-10.5h6v1.5h-6v-1.5zm1.8 7.5v-10.4h1.7v10c0 .5.1.9.3 1.2.2.3.6.4 1.1.4.2 0 .4 0 .6-.1.2-.1.4-.2.5-.2v1.7c-.2.1-.4.1-.6.2-.2.1-.5.1-.8.1-.9 0-1.5-.2-2.1-.8-.4-.5-.7-1.2-.7-2.1zm6.5 2.7v-10.2h1.7v10.2h-1.7zm.9-12.1c-.3 0-.6-.1-.9-.4-.2-.2-.4-.5-.4-.9 0-.3.1-.6.4-.9.2-.2.5-.4.9-.4.3 0 .6.1.9.4.2.2.4.5.4.9 0 .3-.1.6-.4.9-.3.3-.6.4-.9.4zm8.1 12.4c-1 0-1.9-.2-2.7-.7-.8-.5-1.4-1.1-1.8-1.9-.4-.8-.7-1.7-.7-2.8 0-1 .2-1.9.7-2.8.4-.8 1.1-1.5 1.8-2 .8-.5 1.7-.7 2.7-.7 1 0 1.9.2 2.7.7.8.5 1.4 1.1 1.9 2 .4.8.7 1.7.7 2.7 0 1-.2 1.9-.7 2.8-.4.8-1.1 1.5-1.9 1.9-.8.5-1.7.8-2.7.8zm0-1.6c.6 0 1.2-.2 1.7-.5s1-.8 1.3-1.3c.3-.6.5-1.3.5-2.1s-.2-1.5-.5-2.1c-.3-.6-.8-1-1.3-1.3-.5-.3-1.1-.5-1.7-.5-.6 0-1.2.2-1.7.5s-1 .7-1.3 1.3c-.3.6-.5 1.3-.5 2.1s.2 1.5.5 2.1c.3.6.8 1 1.3 1.3.5.4 1.1.5 1.7.5zm7 1.3v-10.2H87v1.5h.1c.3-.5.7-.9 1.3-1.3.6-.4 1.3-.5 2-.5 1.2 0 2.2.4 2.8 1.1.6.7 1 1.7 1 2.9v6.5h-1.7V107c0-1-.2-1.7-.7-2.1-.5-.4-1.1-.6-1.8-.6-.6 0-1.1.2-1.5.5-.4.3-.8.7-1 1.2-.2.5-.4 1-.4 1.6v5.7h-1.7zm15.1-10.2h6v1.5h-6v-1.5zm1.8 7.5v-10.4h1.7v10c0 .5.1.9.3 1.2.2.3.6.4 1.1.4.2 0 .4 0 .6-.1.2-.1.4-.2.5-.2v1.7c-.2.1-.4.1-.6.2-.2.1-.5.1-.8.1-.9 0-1.5-.2-2.1-.8-.4-.5-.7-1.2-.7-2.1zm10.5 3c-1 0-1.9-.2-2.7-.7-.8-.5-1.4-1.1-1.8-1.9-.4-.8-.7-1.7-.7-2.8 0-1 .2-1.9.7-2.8.4-.8 1.1-1.5 1.8-2 .8-.5 1.7-.7 2.7-.7 1 0 1.9.2 2.7.7.8.5 1.4 1.1 1.9 2 .4.8.7 1.7.7 2.7 0 1-.2 1.9-.7 2.8-.4.8-1.1 1.5-1.9 1.9-.8.5-1.7.8-2.7.8zm0-1.6c.6 0 1.2-.2 1.7-.5s1-.8 1.3-1.3c.3-.6.5-1.3.5-2.1s-.2-1.5-.5-2.1c-.3-.6-.8-1-1.3-1.3-.5-.3-1.1-.5-1.7-.5-.6 0-1.2.2-1.7.5s-1 .7-1.3 1.3c-.3.6-.5 1.3-.5 2.1s.2 1.5.5 2.1c.3.6.8 1 1.3 1.3.5.4 1.1.5 1.7.5zm12.2 1.3V99h2.1l7.2 11.4h.1l-.1-2.8V99h1.7v14.3h-1.8l-7.5-11.9h-.1l.1 2.8v9.2H125zm18.2.3c-1 0-1.9-.2-2.6-.7-.8-.5-1.4-1.1-1.8-1.9-.4-.8-.6-1.7-.6-2.8 0-1 .2-1.9.6-2.7.4-.8 1-1.5 1.7-2s1.6-.8 2.6-.8 1.9.2 2.6.7c.7.4 1.3 1.1 1.7 1.8.4.8.6 1.7.6 2.7v.5h-8.8V107h7c0-.3-.1-.6-.2-.9-.1-.3-.3-.6-.5-.9-.2-.3-.6-.5-.9-.7-.4-.2-.8-.3-1.4-.3-.7 0-1.2.2-1.7.5s-.8.8-1.1 1.4c-.3.6-.4 1.2-.4 2 0 .9.2 1.6.5 2.2.3.6.8 1 1.3 1.3.5.3 1.1.4 1.7.4.8 0 1.4-.2 1.8-.5.5-.4.9-.8 1.2-1.3l1.4.7c-.4.8-1 1.4-1.7 1.9-1 .5-1.9.8-3 .8zm5.5-.3 4.1-5.9h.2l2.9-4.3h2l-4 5.6h-.1l-3.1 4.6h-2zm.1-10.2h1.9l3.2 4.5h.1l4 5.7h-2l-3-4.5h-.1l-4.1-5.7zm9.9 0h6v1.5h-6v-1.5zm1.8 7.5v-10.4h1.7v10c0 .5.1.9.3 1.2.2.3.6.4 1.1.4.2 0 .4 0 .6-.1.2-.1.4-.2.5-.2v1.7c-.2.1-.4.1-.6.2-.2.1-.5.1-.8.1-.9 0-1.5-.2-2.1-.8-.4-.5-.7-1.2-.7-2.1zm11.2 2.7V99h4.8c.8 0 1.5.2 2.2.5.7.4 1.2.8 1.6 1.5.4.6.6 1.4.6 2.2 0 .8-.2 1.6-.6 2.2-.4.6-.9 1.1-1.6 1.5-.7.4-1.4.5-2.2.5h-3.9v-1.6h4c.6 0 1-.1 1.4-.4.4-.3.7-.6.9-1 .2-.4.3-.8.3-1.2 0-.4-.1-.8-.3-1.2-.2-.4-.5-.7-.9-1-.4-.3-.9-.4-1.4-.4h-3.2v12.7h-1.7zm14 .3c-.8 0-1.4-.1-2-.4-.6-.3-1-.7-1.3-1.2-.3-.5-.5-1.1-.5-1.8 0-.8.2-1.4.6-1.9.4-.5.9-.9 1.5-1.2.7-.3 1.4-.4 2.2-.4.4 0 .9 0 1.2.1.4.1.7.2 1 .3.3.1.5.2.7.3v-.6c0-.8-.3-1.4-.8-1.8-.5-.5-1.2-.7-2-.7-.6 0-1.1.1-1.6.4-.5.2-.9.6-1.1 1l-1.3-1c.3-.4.6-.7 1-1 .4-.3.9-.5 1.4-.7.5-.2 1.1-.2 1.6-.2 1.4 0 2.5.4 3.2 1.1.8.7 1.2 1.7 1.2 3v6.5h-1.6v-1.5h-.1c-.2.3-.4.6-.7.8-.3.3-.7.5-1.1.7-.5.1-.9.2-1.5.2zm.2-1.5c.6 0 1.1-.1 1.6-.4.5-.3.9-.7 1.2-1.2.3-.5.5-1 .5-1.6-.3-.2-.7-.4-1.2-.5-.5-.1-1-.2-1.5-.2-1 0-1.7.2-2.1.6-.4.4-.7.9-.7 1.5s.2 1 .6 1.4c.4.2 1 .4 1.6.4zm7.4 1.2v-10.2h1.7v10.2h-1.7zm.8-12.1c-.3 0-.6-.1-.9-.4-.2-.2-.4-.5-.4-.9 0-.3.1-.6.4-.9.2-.2.5-.4.9-.4.3 0 .6.1.9.4.2.2.4.5.4.9 0 .3-.1.6-.4.9-.3.3-.5.4-.9.4zm3.5 12.1v-10.2h1.6v1.5h.1c.3-.5.7-.9 1.3-1.3.6-.4 1.3-.5 2-.5 1.2 0 2.2.4 2.8 1.1.6.7 1 1.7 1 2.9v6.5h-1.7V107c0-1-.2-1.7-.7-2.1-.5-.4-1.1-.6-1.8-.6-.6 0-1.1.2-1.5.5-.4.3-.8.7-1 1.2-.2.5-.4 1-.4 1.6v5.7h-1.7zm10-10.2h6v1.5h-6v-1.5zm1.8 7.5v-10.4h1.7v10c0 .5.1.9.3 1.2.2.3.6.4 1.1.4.2 0 .4 0 .6-.1.2-.1.4-.2.5-.2v1.7c-.2.1-.4.1-.6.2-.2.1-.5.1-.8.1-.9 0-1.5-.2-2.1-.8-.4-.5-.7-1.2-.7-2.1zM418.1 55.2c-1.8 0-3.3 1.5-3.3 3.3 0 1.5 1 2.7 2.3 3.1V83h2V61.6c1.3-.4 2.3-1.7 2.3-3.1 0-1.8-1.5-3.3-3.3-3.3zM546.5 58.6c0-1.8-1.5-3.3-3.3-3.3s-3.3 1.5-3.3 3.3c0 1.5 1 2.7 2.3 3.1v21.2h2V61.7c1.3-.4 2.3-1.6 2.3-3.1z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;
&lt;/figure&gt;
&lt;p&gt;Depending on the website, there may be few to no interactions—such as pages of mostly text and images with few to no interactive elements. Or, in the case of websites such as text editors or games, there could be hundreds—even thousands—of interactions. In either case, where there&#39;s a high INP, the user experience is at risk.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Read to learn more:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/inp/&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;It takes time and effort to improve INP, but the reward is a better user experience. In this guide, a path to improving INP will be explored.&lt;/p&gt;
&lt;h2 id=&quot;figure-out-whats-causing-poor-inp&quot;&gt;Figure out what&#39;s causing poor INP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#figure-out-whats-causing-poor-inp&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before you can fix slow interactions, you&#39;ll need data to tell you if your website&#39;s INP is poor or needs improvement. Once you have that information, you can move into the lab to begin diagnosing slow interactions, and work your way toward a solution.&lt;/p&gt;
&lt;h3 id=&quot;find-slow-interactions-in-the-field&quot;&gt;Find slow interactions in the field &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#find-slow-interactions-in-the-field&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ideally, your journey in optimizing INP will start with &lt;a href=&quot;https://web.dev/lab-and-field-data-differences/#field-data&quot;&gt;field data&lt;/a&gt;. At its best, field data from a Real User Monitoring (RUM) provider will give you not only a page&#39;s INP value, but also contextual data that highlights what specific interaction was responsible for the INP value itself, whether the interaction occurred during or after page load, the type of interaction (click, keypress, or tap), and other valuable information.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Read to learn more:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/find-slow-interactions-in-the-field/&quot;&gt;Find slow interactions in the field&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;If you&#39;re not relying on a RUM provider to get field data, the &lt;a href=&quot;https://web.dev/find-slow-interactions-in-the-field/&quot;&gt;INP field data guide&lt;/a&gt; advises &lt;a href=&quot;https://web.dev/find-slow-interactions-in-the-field/#get-field-data-quickly-with-crux&quot;&gt;using the Chrome User Experience Report (CrUX) via PageSpeed Insights&lt;/a&gt; to help fill in the gaps. CrUX is the official dataset of the Core Web Vitals program and provides a high-level summary of metrics for millions of websites, including INP. However, CrUX often does not provide the contextual data you&#39;d get from a RUM provider to help you to analyze issues. Because of this, we still recommend that sites use a RUM provider when possible, or implement their own RUM solution to supplement what is available in CrUX.&lt;/p&gt;
&lt;h3 id=&quot;diagnose-slow-interactions-in-the-lab&quot;&gt;Diagnose slow interactions in the lab &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#diagnose-slow-interactions-in-the-lab&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ideally, you&#39;ll want to start testing in the lab once you have field data that suggests you have slow interactions. In the absence of field data, there are some strategies for identifying slow interactions in the lab. Such strategies include following common user flows and testing interactions along the way, as well as interacting with the page during load—when the main thread is often busiest—in order to surface slow interactions during that crucial part of the user experience.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Read to learn more:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/diagnose-slow-interactions-in-the-lab/&quot;&gt;Diagnose slow interactions in the lab&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;optimize-interactions&quot;&gt;Optimize interactions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#optimize-interactions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once you&#39;ve identified a slow interaction and &lt;a href=&quot;https://web.dev/diagnose-slow-interactions-in-the-lab/&quot;&gt;can reproduce it in the lab&lt;/a&gt;, the next step is to optimize it. Interactions can be broken down into three phases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The input delay, which starts when the user initiates an interaction with the page, and ends when the event callbacks for the interaction begin to run.&lt;/li&gt;
&lt;li&gt;The processing time, which consists of the time it takes for event callbacks to run to completion.&lt;/li&gt;
&lt;li&gt;The presentation delay, which is the time it takes for the browser to present the next frame which contains the visual result of the interaction.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The sum of these three phases is the total interaction latency. Every single phase of an interaction contributes some amount of time to total interaction latency, so it&#39;s important to know how you can optimize each part of the interaction so it runs for as little time as possible.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; When optimizing interactions, it&#39;s important to understand that each &lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Browsing_context&quot;&gt;browsing context&lt;/a&gt; will have its own main thread. This means that the top-level page will have a main thread, but each &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; element on the page will have its own main thread as well. INP will be reported at the page-level including any slow interactions on the page or any iframes within that page. Ensure you understand which frame an interaction is happening in, to understand which main thread to look at. However, even with multiple main threads, resource-constrained devices can result in impact being felt across these seemingly independent threads. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;identify-and-reduce-input-delay&quot;&gt;Identify and reduce input delay &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#identify-and-reduce-input-delay&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When a user interacts with a page, the first part of that interaction is the &lt;em&gt;input delay&lt;/em&gt;. Depending on other activity on the page, input delays can be considerable in length. This could be due to activity occurring on the main thread (perhaps due to scripts loading, parsing and compiling), fetch handling, timer functions, or even from other interactions that occur in quick succession and overlap with one another.&lt;/p&gt;
&lt;p&gt;Whatever the source of an interaction&#39;s input delay, you&#39;ll want to reduce input delay to a minimum so that interactions can begin running event callbacks as soon as possible.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Read to learn more:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/optimize-input-delay/&quot;&gt;Optimize input delay&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;the-relationship-between-script-evaluation-and-long-tasks-during-startup&quot;&gt;The relationship between script evaluation and long tasks during startup &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#the-relationship-between-script-evaluation-and-long-tasks-during-startup&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A critical aspect of interactivity in the page lifecycle is during startup. As a page loads, it will initially render, but it&#39;s important to remember that just because a page has &lt;em&gt;rendered&lt;/em&gt;, doesn&#39;t mean that the page is finished &lt;em&gt;loading&lt;/em&gt;. Depending on how many resources a page requires to become fully functional, it&#39;s possible that users may attempt to interact with the page while it&#39;s still loading.&lt;/p&gt;
&lt;p&gt;One thing that can extend an interaction&#39;s input delay while a page loads is script evaluation. After a JavaScript file has been fetched from the network, the browser still has work to do before that JavaScript can run; that work includes parsing a script to ensure its syntax is valid, compiling it into bytecode, and then finally executing it.&lt;/p&gt;
&lt;p&gt;Depending on the size of a script, this work can introduce long tasks on the main thread, which will delay the browser from responding to other user interactions. To keep your page responsive to user input during page load, it&#39;s important to understand what you can do to reduce the likelihood of long tasks during page load so the page stays snappy.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Read to learn more:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/script-evaluation-and-long-tasks/&quot;&gt;Script evaluation and long tasks&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;optimize-event-callbacks&quot;&gt;Optimize event callbacks &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#optimize-event-callbacks&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The input delay is only the first part of what INP measures. You&#39;ll also need to make sure that the event callbacks that run in response to a user interaction can complete as quickly as possible.&lt;/p&gt;
&lt;h4 id=&quot;yield-to-the-main-thread-often&quot;&gt;Yield to the main thread often &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#yield-to-the-main-thread-often&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The best general advice in optimizing event callbacks is to do as little work as possible in them. However, your interaction logic may be complex, and you may only be able to marginally reduce the work they do.&lt;/p&gt;
&lt;p&gt;If you find this is the case for your website, the next thing you can try is to break up the work in event callbacks into separate tasks. This prevents the collective work from becoming a long task that blocks the main thread, which allows other interactions that otherwise would be waiting on the main thread to run sooner.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setTimeout&lt;/code&gt; is one way to break up tasks, because the callback passed to it runs in a new task. You can &lt;a href=&quot;https://web.dev/optimize-long-tasks/#manually-defer-code-execution&quot;&gt;use &lt;code&gt;setTimeout&lt;/code&gt; by itself&lt;/a&gt; or abstract its use into a separate function &lt;a href=&quot;https://web.dev/optimize-long-tasks/#use-asyncawait-to-create-yield-points&quot;&gt;for more ergonomic yielding&lt;/a&gt;.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Read to learn more:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/optimize-long-tasks/&quot;&gt;Optimize long tasks&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Yielding indiscriminately is better than not yielding at all—however, there is a more nuanced way of yielding to the main thread, and that involves only yielding immediately after an event callback that updates the user interface so rendering logic can run sooner.&lt;/p&gt;
&lt;h4 id=&quot;yield-to-allow-rendering-work-to-occur-sooner&quot;&gt;Yield to allow rendering work to occur sooner &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#yield-to-allow-rendering-work-to-occur-sooner&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A more advanced yielding technique involves structuring the code in your event callbacks to limit what gets run to just the logic required to apply visual updates for the next frame. Everything else can be deferred to a subsequent task. This not only keeps callbacks light and nimble, but it also improves rendering time for interactions by not allowing visual updates to block on event callback code.&lt;/p&gt;
&lt;p&gt;For example, imagine a rich text editor that formats text as you type, but also updates other aspects of the UI in response to what you&#39;ve written (such as word count, highlighting spelling mistakes, and other important visual feedback). In addition, the application may also need to save what you&#39;ve written so that if you leave and return, you haven&#39;t lost any work.&lt;/p&gt;
&lt;p&gt;In this example, the following four things need to happen in response to characters typed by the user. However, only the first item needs to be done before the next frame is presented.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update the text box with what the user typed and apply any required formatting.&lt;/li&gt;
&lt;li&gt;Update the part of the UI that displays the current word count.&lt;/li&gt;
&lt;li&gt;Run logic to check for spelling mistakes.&lt;/li&gt;
&lt;li&gt;Save the most recent changes (either locally or to a remote database).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The code to do this might look something like the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;textBox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;input&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;inputEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Update the UI immediately, so the changes the user made&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// are visible as soon as the next frame is presented.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;updateTextBox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inputEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Use `setTimeout` to defer all other work until at least the next&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// frame by queuing a task in a `requestAnimationFrame()` callback.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; textBox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;updateWordCount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;checkSpelling&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;saveChanges&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The following visualization shows how deferring any non-critical updates until after the next frame can reduce the processing time and thus the overall interaction latency.&lt;/p&gt;
&lt;figure&gt;
  &lt;a href=&quot;https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/Me4oU1cqMPOqEaEg2XAP.svg&quot; rel=&quot;noopener&quot;&gt;
    &lt;img alt=&quot;A depiction of a keyboard interaction and subsequent tasks in two scenarios. In the top figure, the render-critical task and all subsequent background tasks run synchronously until the opportunity to present a frame has arrived. In the bottom figure, the render-critical work runs first, then yields to the main thread to present a new frame sooner. The background tasks run thereafter.&quot; decoding=&quot;async&quot; height=&quot;495&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 770px) 770px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/MN6LranaUtJMlGZEA66T.png?auto=format&amp;w=1540 1540w&quot; width=&quot;770&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;
    Click the above figure to see a high-resolution version.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;While the use of &lt;code&gt;setTimeout()&lt;/code&gt; inside a &lt;code&gt;requestAnimationFrame()&lt;/code&gt; call in the previous code example is admittedly a bit esoteric, it is an effective method that works in all browsers to ensure that the non-critical code does not block the next frame.&lt;/p&gt;
&lt;h4 id=&quot;avoid-layout-thrashing&quot;&gt;Avoid layout thrashing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#avoid-layout-thrashing&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Layout thrashing—sometimes called forced synchronous layout—is a rendering performance problem where layout occurs synchronously. It occurs when you update styles in JavaScript, and then read them in the same task—and &lt;a href=&quot;https://gist.github.com/paulirish/5d52fb081b3570c81e3a&quot; rel=&quot;noopener&quot;&gt;there are many properties in JavaScript that can cause layout thrashing&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A visualization of layout thrashing as shown in the performance panel of Chrome DevTools.&quot; decoding=&quot;async&quot; height=&quot;336&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/06CXJcBsqO6kdj1Bjml7.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    An example of layout thrashing, as shown in the performance panel of Chrome DevTools. Rendering tasks that involve layout thrashing will be noted with a red triangle at the upper right corner of the portion of the call stack, often labeled &lt;strong&gt;Recalculate Style&lt;/strong&gt; or &lt;strong&gt;Layout&lt;/strong&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Layout thrashing is a performance bottleneck because by updating styles and then immediately requesting the values of those styles in JavaScript, the browser is forced to do synchronous layout work it otherwise could have waited to perform asynchronously later on after event callbacks have finished running.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Read to learn more:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/avoid-large-complex-layouts-and-layout-thrashing/&quot;&gt;Avoid large, complex layouts and layout thrashing&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;minimize-presentation-delay&quot;&gt;Minimize presentation delay &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#minimize-presentation-delay&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;em&gt;presentation delay&lt;/em&gt; of an interaction marks spans from when an interaction&#39;s event callbacks have finished running, up to the point at which the browser is able to paint the next frame that shows the resulting visual changes.&lt;/p&gt;
&lt;h4 id=&quot;minimize-dom-size&quot;&gt;Minimize DOM size &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#minimize-dom-size&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When a page&#39;s DOM is small, rendering work usually finishes quickly. However, when DOMs get very large, rendering work tends to scale with increasing DOM size. The relationship between rendering work and DOM size isn&#39;t a linear one, but large DOMs do require more work to render than small DOMs. A large DOM is problematic in two cases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;During the initial page render, where a large DOM requires a lot of work to render the page&#39;s initial state.&lt;/li&gt;
&lt;li&gt;In response to a user interaction, where a large DOM can cause rendering updates to be very expensive, and therefore increase the time it takes for the browser to present the next frame.&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Read to learn more:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/dom-size-and-interactivity/&quot;&gt;DOM size and interactivity&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Bear in mind that there are instances in which large DOMs can&#39;t be significantly reduced. While there are approaches you can take to reduce DOM size, such as &lt;a href=&quot;https://web.dev/dom-size-and-interactivity/#how-can-i-reduce-dom-size&quot;&gt;flattening your DOM&lt;/a&gt; or &lt;a href=&quot;https://web.dev/dom-size-and-interactivity/#consider-an-additive-approach&quot;&gt;add to the DOM during user interactions&lt;/a&gt; to keep your initial DOM size small, those techniques may only go so far.&lt;/p&gt;
&lt;h4 id=&quot;use-content-visibility-to-lazily-render-off-screen-elements&quot;&gt;Use &lt;code&gt;content-visibility&lt;/code&gt; to lazily render off-screen elements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#use-content-visibility-to-lazily-render-off-screen-elements&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;One way you can limit the amount of both rendering work during page load and rendering work in response to user interactions is to lean on the CSS &lt;code&gt;content-visibility&lt;/code&gt; property, which effectively amounts to lazily rendering elements as they approach the viewport. While &lt;code&gt;content-visibility&lt;/code&gt; can take some practice to use effectively, it&#39;s worth investigating if the result is lower rendering time that can improve your page&#39;s INP.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Read to learn more:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/content-visibility/&quot;&gt;&lt;code&gt;content-visibility&lt;/code&gt;: the new CSS property that boosts your rendering performance&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;be-aware-of-performance-costs-when-rendering-html-using-javascript&quot;&gt;Be aware of performance costs when rendering HTML using JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#be-aware-of-performance-costs-when-rendering-html-using-javascript&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Where there&#39;s HTML, there&#39;s HTML parsing, and after the browser has finished parsing HTML into a DOM, it must apply styles to it, perform layout calculations, and subsequently render that layout. This is an unavoidable cost, but &lt;em&gt;how&lt;/em&gt; you go about rendering HTML matters.&lt;/p&gt;
&lt;p&gt;When the server sends HTML, it arrives in the browser as a stream. Streaming means that the HTML response from the server is arriving in chunks. The browser optimizes how it handles a stream by incrementally parsing chunks of that stream as they arrive, and rendering them bit by bit. This is a performance optimization in that the browser implicitly yields periodically and automatically during page load, and you get that for free.&lt;/p&gt;
&lt;p&gt;While the first visit to any website will always involve &lt;em&gt;some&lt;/em&gt; amount of HTML, a common approach starts with a minimal initial bit of HTML, and then JavaScript is used to populate the content area. Subsequent updates to that content area also occur as the result of user interactions. This is usually called the &lt;a href=&quot;https://en.wikipedia.org/wiki/Single-page_application&quot; rel=&quot;noopener&quot;&gt;single-page application (SPA) model&lt;/a&gt;. One drawback of this pattern is that, by rendering HTML with JavaScript on the client, you not only get the cost of the JavaScript processing to create that HTML, but also the browser will &lt;em&gt;not&lt;/em&gt; yield until it has finished parsing that HTML, and rendering it.&lt;/p&gt;
&lt;p&gt;It&#39;s vital to remember though, that even websites that &lt;em&gt;aren&#39;t&lt;/em&gt; SPAs will probably involve some amount of HTML rendering through JavaScript as the result of interactions. This is generally fine, so long as you&#39;re not rendering large amounts of HTML on the client, which can delay presentation of the next frame. However, it&#39;s important to understand the performance implications of this approach to rendering HTML in the browser, and how it can impact the responsiveness of your website to user input if you are rendering a lot of HTML via JavaScript.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Read to learn more:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/client-side-rendering-of-html-and-interactivity/&quot;&gt;Client-side rendering of HTML and interactivity&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-inp/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Improving your site&#39;s INP is an iterative process. When you fix a slow interaction in the field, the chances are good that—especially if your website provides lots of interactivity—you&#39;ll start to find other slow interactions, and you&#39;ll need to optimize them too.&lt;/p&gt;
&lt;p&gt;The key to improving INP is persistence. In time, you can get your page&#39;s responsiveness to a place where users are happy with the experience you&#39;re providing them. The chances are also good that as you develop new features for your users, you may need to go through the same process in optimizing interactions specific to them. It will take time and effort, but it&#39;s time and effort well spent.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Hero image from &lt;a href=&quot;https://unsplash.com/&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;, by &lt;a href=&quot;https://unsplash.com/@davidpisnoy&quot; rel=&quot;noopener&quot;&gt;David Pisnoy&lt;/a&gt; and modified in accordance with the &lt;a href=&quot;https://unsplash.com/license&quot; rel=&quot;noopener&quot;&gt;Unsplash license&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
    <author>
      <name>Jeremy Wagner</name>
    </author><author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>How SPA architectures affect Core Web Vitals</title>
    <link href="https://web.dev/vitals-spa-faq/"/>
    <updated>2021-09-14T00:00:00Z</updated>
    <id>https://web.dev/vitals-spa-faq/</id>
    <content type="html" mode="escaped">&lt;p&gt;Since first introducing the &lt;a href=&quot;https://web.dev/vitals/&quot;&gt;Web Vitals&lt;/a&gt; initiative in May of 2020, we
on the Chrome team have received a lot of great questions and feedback about the
program.&lt;/p&gt;
&lt;p&gt;Perhaps the topic we&#39;ve received the most questions about, which is also
probably the hardest question to answer, is how to measure Core Web Vitals in a
&lt;a href=&quot;https://en.wikipedia.org/wiki/Single-page_application&quot; rel=&quot;noopener&quot;&gt;single-page application&lt;/a&gt;
(SPA), as well as how SPA architectures affect Core Web Vitals scores.&lt;/p&gt;
&lt;p&gt;These questions are hard to answer because the problem is quite nuanced, so in
this post we&#39;re going to do our best to answer the most common questions,
providing as much detail and context as we can.&lt;/p&gt;
&lt;p&gt;Before getting into specifics, though, it&#39;s important to state that Google does
not have any preference as to what architecture or technology is used to build a
site. We believe that SPAs and multi-page applications (MPAs) are both capable
of delivering high quality experiences to users, and our intention with the Web
Vitals initiative is to provide metrics that measure the experience independent
of the technology. While this is not possible in every case today (due to
limitations in the web platform), we are &lt;a href=&quot;https://web.dev/vitals-spa-faq/#what-is-google-doing-to-ensure-mpas-do-not-have-an-unfair-advantage-compared-to-spas&quot;&gt;actively working on closing those
gaps&lt;/a&gt;.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Update (February 2023): The work is now available for sites to experiment with. See the &lt;a href=&quot;https://developer.chrome.com/blog/soft-navigations-experiment/&quot;&gt;Experimenting with measuring soft navigations&lt;/a&gt; post for more details. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;frequently-asked-questions&quot;&gt;Frequently asked questions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#frequently-asked-questions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;do-core-web-vitals-metrics-include-spa-route-transitions&quot;&gt;Do Core Web Vitals metrics include SPA route transitions? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#do-core-web-vitals-metrics-include-spa-route-transitions&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;No. Each of the Core Web Vitals metrics is measured relative to the current,
top-level page navigation. If a page dynamically loads new
content and updates the URL of the page in the address bar, it will have no
effect on how the Core Web Vitals metrics are measured. Metric values are not
reset, and the URL associated with each metric measurement is the URL the user
navigated to that initiated the page load.&lt;/p&gt;
&lt;h3 id=&quot;can-the-core-web-vitals-metrics-treat-spa-route-changes-the-same-as-traditional-page-loads&quot;&gt;Can the Core Web Vitals metrics treat SPA route changes the same as traditional page loads? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#can-the-core-web-vitals-metrics-treat-spa-route-changes-the-same-as-traditional-page-loads&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unfortunately, no. Not yet anyway.&lt;/p&gt;
&lt;p&gt;There is no standardized way of building an SPA today, and even among the
popular SPA and routing libraries, the user experience can be quite different
from app to app:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some SPAs update the URL only when loading new &amp;quot;full page&amp;quot; content, whereas
other sites update the URL for tiny content changes or even just UI state
changes.&lt;/li&gt;
&lt;li&gt;Some SPAs update the URL using the History API, whereas others use hash
changes in order to support older browsers (and others do not update the URL
at all).&lt;/li&gt;
&lt;li&gt;Some SPAs load content and then update the URL, whereas others update the URL
before loading content.&lt;/li&gt;
&lt;li&gt;Some SPAs load content all at once, synchronously, in a single JavaScript
task, whereas others transition content in, asynchronously, across multiple
tasks (with no clear transition end event).&lt;/li&gt;
&lt;li&gt;Some SPAs always load content from the network, whereas others preload all
content upfront so that route changes load instantly from memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These differences make defining and identifying what constitutes an SPA route
change, or even an SPA itself, very difficult to do &lt;em&gt;at scale&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In some cases an SPA route change is logically identical to an MPA page load,
and in such cases it would be great if the existing Core Web Vitals metrics
could be applied.&lt;/p&gt;
&lt;p&gt;However, without solid heuristics to reliably identify &amp;quot;real&amp;quot; route changes from
all other URL changes—as well as clear signals marking the beginning and end of
such transitions—reporting Core Web Vitals metrics in these cases would muddy
the data and make it less useful or representative of the real user experience
on the site.&lt;/p&gt;
&lt;h3 id=&quot;is-it-harder-for-spas-to-do-well-on-core-web-vitals-than-mpas&quot;&gt;Is it harder for SPAs to do well on Core Web Vitals than MPAs? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#is-it-harder-for-spas-to-do-well-on-core-web-vitals-than-mpas&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There is nothing inherent in the SPA architecture that would prevent a page in
an SPA from loading just as quickly—and scoring just as well on all of the Core
Web Vitals metrics—as a similar page in an MPA.&lt;/p&gt;
&lt;p&gt;However, properly optimized MPAs do have some advantages in meeting the Core Web
Vitals thresholds that SPAs currently do not. The reason is because with the MPA
architecture, each &amp;quot;page&amp;quot; is loaded as a full-page navigation (rather than
dynamically fetching content and inserting it into the existing page), which
means users who visit an MPA are more likely to load more than one page from the
site, which in turn means that a larger percentage of the distribution of all
page loads for an MPA will involve some or all of the sub-resources being
cached.&lt;/p&gt;
&lt;p&gt;Granted, for an MPA to perform better on the Core Web Vitals metrics than an SPA
requires a few things to be true:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The MPA needs to have optimized sub-resource caching in order to ensure
same-origin page loads are indeed faster than cross-origin page loads at the
75th percentile.&lt;/li&gt;
&lt;li&gt;Users who visit MPAs need to visit multiple pages in order for the site to
receive the caching benefits that result in faster page loads.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since Core Web Vitals assessments &lt;a href=&quot;https://web.dev/defining-core-web-vitals-thresholds/#choice-of-percentile&quot;&gt;consider the 75th
percentile&lt;/a&gt; of page
visits, having more, well-performing page visits in the dataset will increase
the likelihood that the visit at the 75th percentile of the distribution will be
within the recommended thresholds.&lt;/p&gt;
&lt;p&gt;Note that an important thing to consider when comparing Core Web Vitals scores
is how the data is aggregated—that is, whether the dataset in the distribution
includes all pages from your site or origin, or just page loads for a particular
page URL.&lt;/p&gt;
&lt;p&gt;When aggregating the scores of all pages in an origin, individual fast pages can
improve the 75th percentile for the origin as a whole. However, when aggregating
by individual pages, the scores of one page will not affect the scores of the
next. In other words, when aggregating the scores of an MPA by page, fast cache
loads seen on the checkout page will &lt;em&gt;not&lt;/em&gt; improve the scores of slow initial
loads experienced on the site&#39;s &lt;a href=&quot;https://en.wikipedia.org/wiki/Landing_page&quot; rel=&quot;noopener&quot;&gt;landing
page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can check your site&#39;s score for different aggregation methods using
&lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed Insights&lt;/a&gt; or
the &lt;a href=&quot;https://developer.chrome.com/blog/chrome-ux-report-api/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience Report
API&lt;/a&gt;,
which reports scores for both individual page URLs and the entire origin.&lt;/p&gt;
&lt;p&gt;Another way the SPA architecture can affect Core Web Vitals scores is for
metrics that consider the full lifespan of a page. Since users visiting SPAs
tend to stay on the same &amp;quot;page&amp;quot; for the entire session, metrics that accumulate
over time can be harsher on SPAs than MPAs.&lt;/p&gt;
&lt;p&gt;In April 2021, we announced &lt;a href=&quot;https://web.dev/evolving-cls/&quot;&gt;changes to the CLS metric&lt;/a&gt; that
partially addressed this problem. Previously CLS would accumulate over the
entire page lifespan, whereas now it only accumulates over a specific window of
time—essentially the worst burst of layout shifts on a given page.&lt;/p&gt;
&lt;p&gt;However, even with the new CLS definition, SPAs are still at a disadvantage
because the CLS value doesn&#39;t &amp;quot;reset&amp;quot; after a route transitions like it does
with full page loads in an MPA. This can also lead to confusion because layout
shifts that occur after a route transition will be attributed to the URL of the
page when it was loaded, not the URL in the address bar at the time of the shift
(&lt;a href=&quot;https://web.dev/vitals-spa-faq/#if-core-web-vitals-scores-are-only-reported-for-an-spa&#39;s-landing-pages-how-can-i-debug-issues-that-occur-on-pages-after-a-route-transition&quot;&gt;more details
below&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&quot;if-spa-architectures-improve-the-user-experience,-shouldnt-that-improvement-be-reflected-in-the-metrics&quot;&gt;If SPA architectures improve the user experience, shouldn&#39;t that improvement be reflected in the metrics? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#if-spa-architectures-improve-the-user-experience,-shouldnt-that-improvement-be-reflected-in-the-metrics&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Yes, it should. Though as mentioned previously, quantifying just how much the
experience has improved is difficult to do at scale, given all the different
ways SPAs are implemented on the web today.&lt;/p&gt;
&lt;p&gt;The truth is the web performance industry (Google included) has historically not
invested nearly as much time and effort into developing &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/&quot;&gt;user-centric
metrics&lt;/a&gt; for the post-load performance of a
page as it has for the page load itself. This isn&#39;t because post-load
performance isn&#39;t important, it&#39;s because post-load UX and interactions are so
much more varied and less well-defined—making it hard to design metrics for
them.&lt;/p&gt;
&lt;p&gt;But even if we did have well defined post-load metrics to measure SPA
performance, we wouldn&#39;t want to ignore the load experience just because the
post load experience got better.&lt;/p&gt;
&lt;p&gt;One of the goals of the Web Vitals initiative is to promote and incentivize good
user experiences across as many aspects of loading and using a web page as
possible. We don&#39;t want to encourage scenarios where bad experiences are
justified if you can have enough good experiences to make up for them. Users
want pages to load fast &lt;em&gt;and&lt;/em&gt; transition to new content fast, and we&#39;ve tried to
design metrics that favor those types of experiences.&lt;/p&gt;
&lt;p&gt;So while it&#39;s true that an MPA version of a site may fare better on the Core Web
Vitals metrics at the 75th percentile than an SPA version of the exact same
site, the SPA version should still strive to meet the &amp;quot;good&amp;quot; threshold. If the
SPA version doesn&#39;t meet the &amp;quot;good&amp;quot; threshold for most users, then the initial
load experience is probably still not perceived as good—even if the subsequent,
in-page navigation experience is excellent.&lt;/p&gt;
&lt;p&gt;In the future we plan to develop metrics that encourage great, post-load
experiences, and we believe this is the best path to incentivize high-quality
SPAs in a way that doesn&#39;t compromise the initial load experience. We have
already delivered a new metric named &lt;a href=&quot;https://web.dev/inp/&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt; that measures interaction latency throughout the entire page lifecycle, and we&#39;re actively working on other
post-load metrics as well, including metrics that measure SPA route transitions
(&lt;a href=&quot;https://web.dev/vitals-spa-faq/#design-new-apis-that-enable-better-spa-measurement&quot;&gt;see below&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&quot;we-switched-our-site-from-an-mpa-to-an-spa-and-our-scores-regressed-is-that-expected&quot;&gt;We switched our site from an MPA to an SPA and our scores regressed. Is that expected? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#we-switched-our-site-from-an-mpa-to-an-spa-and-our-scores-regressed-is-that-expected&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It depends. There are a number of reasons why your scores could change after a
major architecture migration, but a decrease in the number of warm cache loads
could account for some of the change.&lt;/p&gt;
&lt;p&gt;A quick way to check would be to test both an MPA and SPA version of one of your
landing pages with
&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt;. If the
Lighthouse score is lower on any of the Core Web Vitals metric for the SPA
version, then it&#39;s likely that the load experience did get worse after the
update.&lt;/p&gt;
&lt;h3 id=&quot;should-i-switch-my-site-from-an-spa-to-an-mpa-to-score-better-on-core-web-vitals&quot;&gt;Should I switch my site from an SPA to an MPA to score better on Core Web Vitals? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#should-i-switch-my-site-from-an-spa-to-an-mpa-to-score-better-on-core-web-vitals&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Probably not. You should only switch from an SPA to an MPA if you are not happy
with your SPA stack and you have reason to believe an MPA will provide a better
user experience.&lt;/p&gt;
&lt;p&gt;Over time, as the Core Web Vitals metrics improve and cover more of the full
browsing experience, teams with well-built SPAs that provide great UX should
expect to see their Core Web Vitals scores reflect that.&lt;/p&gt;
&lt;h3 id=&quot;if-core-web-vitals-scores-are-only-reported-for-an-spas-landing-pages,-how-can-i-debug-issues-that-occur-on-pages-after-a-route-transition&quot;&gt;If Core Web Vitals scores are only reported for an SPA&#39;s landing pages, how can I debug issues that occur on &amp;quot;pages&amp;quot; after a route transition? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#if-core-web-vitals-scores-are-only-reported-for-an-spas-landing-pages,-how-can-i-debug-issues-that-occur-on-pages-after-a-route-transition&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Google tools that report field data for the Core Web Vitals metric (like Search
Console and PageSpeed Insights) get their data from the &lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience
Report&lt;/a&gt;
(CrUX). And CrUX aggregates data either by origin or by page URL (that is, the
page URL at load time).&lt;/p&gt;
&lt;p&gt;For all the reasons already listed above, CrUX is not able to aggregate data by
SPA route. However, as a site owner who is familiar with your own architecture,
it is possible to measure this yourself, and many analytics tools allow you to
signal when an SPA route change is occurring and they update your measurement
data accordingly.&lt;/p&gt;
&lt;p&gt;When measuring Web Vitals metrics with an analytics tool, make sure you&#39;re
measuring both the current route URL as well as the original page URL. This will
allow you to both debug individual issues that occur throughout the page
lifecycle as well as aggregate by original page URL in order to match how Google
tools measure and report on the metrics.&lt;/p&gt;
&lt;p&gt;For more details and best practices on this topic, see: &lt;a href=&quot;https://web.dev/debug-performance-in-the-field/&quot;&gt;Debug performance in the
field&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-is-google-doing-to-ensure-mpas-do-not-have-an-unfair-advantage-compared-to-spas&quot;&gt;What is Google doing to ensure MPAs do not have an unfair advantage compared to SPAs? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#what-is-google-doing-to-ensure-mpas-do-not-have-an-unfair-advantage-compared-to-spas&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As mentioned above, a properly-optimized MPA can, in some cases, report better
Web Vitals scores at the 75th percentile due to the fact that it will likely
have a higher percentage of cached page visits. Conversely, real improvements to
the user experience in properly-optimized SPAs are not currently being captured
by any of the Core Web Vitals metrics.&lt;/p&gt;
&lt;p&gt;At Google, we recognize that this creates incentives that do not fully align
with the goals of the Web Vitals initiative, and we are actively looking at ways
to fix this. Currently, we&#39;re exploring two potential solutions, one short term
and one longer term:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Assess cross-origin and same-origin page visits separately.&lt;/li&gt;
&lt;li&gt;Design new APIs that enable better SPA measurement.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;assess-cross-origin-and-same-origin-page-visits-separately&quot;&gt;Assess cross-origin and same-origin page visits separately &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#assess-cross-origin-and-same-origin-page-visits-separately&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Today the Core Web Vitals metrics aggregate all page visits into a single
bucket—they do not differentiate between new versus returning visits or landing
pages versus checkout pages or any other aggregation type where cache state
could have an effect on performance.&lt;/p&gt;
&lt;p&gt;One way to normalize the differences between SPA and MPA performance would be to
apply different weighting to different types of visits, potentially even with
completely different &lt;a href=&quot;https://web.dev/defining-core-web-vitals-thresholds/&quot;&gt;threshold
recommendations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While we definitely do want to reward effective cache implementations, we don&#39;t
want fast intra-site navigations to be able to cover up for slow landing page
loads. We also don&#39;t want to incentivize sites to break up long pages into a
collection of shorter pages just for the sake of improving metric scores.&lt;/p&gt;
&lt;p&gt;By separately assessing cross-origin and same-origin page visits we can help
ensure that both types of experiences are important without letting the relative
popularity of one type on a given site skew the distribution of any particular
metric.&lt;/p&gt;
&lt;h4 id=&quot;design-new-apis-that-enable-better-spa-measurement&quot;&gt;Design new APIs that enable better SPA measurement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#design-new-apis-that-enable-better-spa-measurement&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Another solution that is actively being worked on (in parallel to the above) is
a new &lt;a href=&quot;https://web.dev/app-history-api/&quot;&gt;App History API&lt;/a&gt;, which would help standardize current
SPA patterns and make it easier to measure and understand SPA usage at scale.&lt;/p&gt;
&lt;p&gt;The App History API introduces a new
&lt;a href=&quot;https://wicg.github.io/app-history/#navigate-event&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;navigate&lt;/code&gt;&lt;/a&gt; event, which
has two key features specific to SPA measurement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A
&lt;a href=&quot;https://wicg.github.io/app-history/#ref-for-dom-apphistorynavigateevent-userinitiated%E2%91%A0&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;userInitiated&lt;/code&gt;&lt;/a&gt;
flag, which will only be set to true if the navigation was initiated via a
link click, form submission, or the browser&#39;s back or forward UI.&lt;/li&gt;
&lt;li&gt;A
&lt;a href=&quot;https://wicg.github.io/app-history/#ref-for-dom-apphistorynavigateevent-transitionwhile%E2%91%A0%E2%91%A8&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;transitionWhile()&lt;/code&gt;&lt;/a&gt;
method, which takes a promise allowing the developer to signal when the work
they&#39;ve initiated to perform the navigation is complete.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;userInitiated&lt;/code&gt; flag can be used to determine a semantic starting point for
an SPA route transition, indicating clear user intent. The &lt;code&gt;transitionWhile()&lt;/code&gt;
promise resolving can help the browser correlate paints with the specific route
transition, such that it may be able to determine the largest contentful paint
related to that transition.&lt;/p&gt;
&lt;p&gt;Building on top of the idea presented in the previous section, it might even be
possible to aggregate SPA route transition time into the same bucket as
same-origin page loads in an MPA. This is exciting because it would allow a site
migrating from an MPA to an SPA to actually compare the performance before and
after.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Update (February 2023): See the &lt;a href=&quot;https://developer.chrome.com/blog/soft-navigations-experiment/&quot;&gt;Experimenting with measuring soft navigations&lt;/a&gt; post for more information on a new &amp;quot;soft-navigations&amp;quot; API that is intended to allow better Core Web Vitals measurements for SPAs. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Of course, more research is needed before we&#39;ll know whether we can accurately
make these determinations. If you have suggestions or feedback on these
proposals, please email
&lt;a href=&quot;mailto:web-vitals-feedback@googlegrouops.com&quot; rel=&quot;noopener&quot;&gt;web-vitals-feedback@googlegroups.com&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final thoughts &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-spa-faq/#final-thoughts&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Google is deeply committed to improving the Web Vitals metrics, and ensuring
they measure and incentivize high-quality experiences that are important to
users. That being said, we do acknowledge that measurement gaps exist today. The
metrics do not currently cover every aspect of user experience, but we are
actively working to close these gaps.&lt;/p&gt;
&lt;p&gt;Despite the current limitations, we believe the areas the existing metrics do
capture are critical to the health and success of the web, and to the extent
that sites (regardless of architecture) do not meet the recommended thresholds,
we believe there is room for improvement.&lt;/p&gt;
&lt;p&gt;I hope this post has helped shed some light on this complex and nuanced subject.
As always, if you have feedback on the current or future Web Vitals metrics,
please email
&lt;a href=&quot;mailto:web-vitals-feedback@googlegrouops.com&quot; rel=&quot;noopener&quot;&gt;web-vitals-feedback@googlegroups.com&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author><author>
      <name>Yoav Weiss</name>
    </author>
  </entry>
  
  <entry>
    <title>Why lab and field data can be different (and what to do about it)</title>
    <link href="https://web.dev/lab-and-field-data-differences/"/>
    <updated>2021-08-17T00:00:00Z</updated>
    <id>https://web.dev/lab-and-field-data-differences/</id>
    <content type="html" mode="escaped">&lt;p&gt;Google provides &lt;a href=&quot;https://web.dev/vitals-tools/&quot;&gt;a number of tools&lt;/a&gt; to help site owners monitor
their &lt;a href=&quot;https://web.dev/vitals/#core-web-vitals&quot;&gt;Core Web Vitals&lt;/a&gt; scores. These tools fall into
two main categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tools that report &lt;strong&gt;lab data&lt;/strong&gt;—data collected in a controlled environment with
predefined device and network settings.&lt;/li&gt;
&lt;li&gt;Tools that report &lt;strong&gt;field data&lt;/strong&gt;—data collected from the real users visiting
your site.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The problem is that sometimes the data reported by lab tools can be quite a bit
different from the data reported by field tools! Your lab data might indicate
that your site performs great, but your field data suggests it needs
improvement. Alternatively, your field data may say all your pages are good, but
your lab data may report a very low score.&lt;/p&gt;
&lt;p&gt;The following real example of a PageSpeed Insights report from web.dev shows
that in some cases lab and field data can be different across all of the Core
Web Vitals metrics:&lt;/p&gt;
&lt;img alt=&quot;Screenshot of a PageSpeed Insights report with conflicting lab and field data&quot; decoding=&quot;async&quot; height=&quot;509&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/YvQK3wA9AQ2fmEuNSzKK.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Differences between tools is an understandable source of confusion for
developers. This post explains the main reasons these differences could
exist—with specific examples covering each of the Core Web Vitals metrics—and
what to do when you find differences on your pages.&lt;/p&gt;
&lt;h2 id=&quot;lab-data-versus-field-data&quot;&gt;Lab data versus field data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#lab-data-versus-field-data&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To understand why lab and field tools might report different values—even for the
exact same web page—you need to understand the difference between lab and field
data.&lt;/p&gt;
&lt;h3 id=&quot;lab-data&quot;&gt;Lab data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#lab-data&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Lab data is determined by loading a web page in a controlled environment with a
predefined set of network and device conditions. These conditions are known as a
&lt;em&gt;lab&lt;/em&gt; environment, sometimes also referred to as a &lt;em&gt;synthetic&lt;/em&gt; environment.&lt;/p&gt;
&lt;p&gt;Chrome tools that report lab data are generally running
&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The purpose of a lab test is to control for as many factors as you can, so the
results are (as much as possible) consistent and reproducible from run to run.&lt;/p&gt;
&lt;h3 id=&quot;field-data&quot;&gt;Field data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#field-data&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Field data is determined by monitoring all users who visit a page and measuring
a given set of performance metrics for each one of those users&#39; individual
experiences. Because field data is based on real-user visits, it reflects the
actual devices, network conditions, and geographic locations of your users.&lt;/p&gt;
&lt;p&gt;Field data is also commonly known as &lt;a href=&quot;https://en.wikipedia.org/wiki/Real_user_monitoring&quot; rel=&quot;noopener&quot;&gt;Real User Monitoring
(RUM)&lt;/a&gt; data; the two terms
are interchangeable.&lt;/p&gt;
&lt;p&gt;Chrome tools that report &lt;em&gt;field data&lt;/em&gt; generally get that data from the &lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome
User Experience Report
(CrUX)&lt;/a&gt;.
It&#39;s also common (and recommended) for site owners to &lt;a href=&quot;https://web.dev/vitals-field-measurement-best-practices/&quot;&gt;collect field data
themselves&lt;/a&gt; because it can provide
&lt;a href=&quot;https://web.dev/vitals-ga4/&quot;&gt;more actionable insights&lt;/a&gt; than just using CrUX alone.&lt;/p&gt;
&lt;p&gt;The most important thing to understand about field data is that it is not just
one number, it&#39;s a distribution of numbers. That is, for some people who visit
your site, it may load very quickly, while for others it may load very slowly.
The &lt;em&gt;field data&lt;/em&gt; for your site is the complete set of all performance data
collected from your users.&lt;/p&gt;
&lt;p&gt;As an example, CrUX reports show a distribution of performance metrics from real
Chrome users over a 28-day period. If you look at almost any CrUX report you can
see that some users who visit a site might have a very good experience while
others might have a very poor experience.&lt;/p&gt;
&lt;p&gt;If a tool does report a single number for a given metric, it will generally
represent a specific point in the distribution. Tools that report Core Web
Vitals field scores do so &lt;a href=&quot;https://web.dev/defining-core-web-vitals-thresholds/#choice-of-percentile&quot;&gt;using the 75th
percentile&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Looking at LCP from the field data in the screenshot above, you can see a
distribution where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;88% of visits saw an LCP of 2.5 seconds or less (good).&lt;/li&gt;
&lt;li&gt;8% of visits saw an LCP between 2.5 and 4 seconds (needs improvement).&lt;/li&gt;
&lt;li&gt;4% of visits saw an LCP greater than 4 seconds (poor).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the 75th percentile, LCP was 1.8 seconds:&lt;/p&gt;
&lt;img alt=&quot;Distribution of LCP scores in the field&quot; decoding=&quot;async&quot; height=&quot;100&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/HttRJY6Drm09UdmbuyyB.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
&lt;p&gt;Lab data from the same page shows an LCP value of 3.0 second. While this value
is greater than the 1.8 seconds shown in the field data, it&#39;s still a valid LCP
value for this page—it&#39;s one of many values that make up the full distribution
of load experiences.&lt;/p&gt;
&lt;img alt=&quot;LCP score in the lab&quot; decoding=&quot;async&quot; height=&quot;50&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/kztPvvnwuTrCzo318cZP.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
&lt;h2 id=&quot;why-lab-and-field-data-are-different&quot;&gt;Why lab and field data are different &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#why-lab-and-field-data-are-different&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As the above section explains, lab data and field data actually measure very
different things.&lt;/p&gt;
&lt;p&gt;Field data includes a wide variety of network and device conditions as well as a
myriad of different types of user behavior. It also includes any other factors
that affect the user experience, such as browser optimizations like the
&lt;a href=&quot;https://web.dev/bfcache/&quot;&gt;back/forward cache&lt;/a&gt; (bfcache), or platform optimizations like the
&lt;a href=&quot;https://developers.google.com/amp/cache&quot; rel=&quot;noopener&quot;&gt;AMP cache&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By contrast, lab data intentionally limits the number of variables involved. A
lab test consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A single device…&lt;/li&gt;
&lt;li&gt;connected to a single network…&lt;/li&gt;
&lt;li&gt;run from a single geographic location.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The particulars of any given lab test may or may not accurately represent the
75th percentile of field data for a given page or site.&lt;/p&gt;
&lt;p&gt;The controlled environment of the lab is useful when debugging issues or testing
features before deploying to production, but when you control for these factors
you are explicitly not representing the variance that you see in the real world
across all types of networks, device capabilities, or geographic locations. You
are also generally not capturing the performance impact of real-user behavior,
such as scrolling, selecting text, or tapping elements on the page.&lt;/p&gt;
&lt;p&gt;In addition to the possible disconnect between lab conditions and the conditions
of most real-world users, there are also a number of more subtle differences
that are important to understand in order to make the most sense out of your lab
and field data, as well as any differences you may find.&lt;/p&gt;
&lt;p&gt;The next few sections go into detail highlighting the most common reasons there
could be differences between lab data and field data for each of the Core Web
Vitals metrics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/cls/&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;lcp&quot;&gt;LCP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#lcp&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;different-lcp-elements&quot;&gt;Different LCP elements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#different-lcp-elements&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The LCP element identified in a lab test may not be the same LCP
element users see when visiting your page.&lt;/p&gt;
&lt;p&gt;If you run a Lighthouse report for a given page, it&#39;s going to return the same
LCP element every single time. But if you look at field data for the same page,
you&#39;ll usually find a variety of different LCP elements, which depend on a
number of circumstances specific to each page visit.&lt;/p&gt;
&lt;p&gt;For example, the following factors could all contribute to a different LCP
element being determined for the same page:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Different device screen sizes result in different elements being visible
within the  viewport.&lt;/li&gt;
&lt;li&gt;If the user is logged in, or if personalized content is being shown in some
way, the LCP element could be very different from user to user.&lt;/li&gt;
&lt;li&gt;Similar to the previous point, if an A/B test is running on the page it could
result in very different elements being displayed.&lt;/li&gt;
&lt;li&gt;The set of fonts installed on the user&#39;s system can affect the size of text on
the page (and thus which element is the LCP element).&lt;/li&gt;
&lt;li&gt;Lab tests are usually run on a page&#39;s &amp;quot;base&amp;quot; URL—without any query parameters
or hash fragments added. But in the real world, users often share URLs
containing a &lt;a href=&quot;https://web.dev/text-fragments/#fragment-identifiers&quot;&gt;fragment identifier&lt;/a&gt; or
&lt;a href=&quot;https://web.dev/text-fragments/#text-fragments&quot;&gt;text fragment&lt;/a&gt;, so the LCP element may
actually be from the middle or bottom of the page (rather than &amp;quot;above the
fold&amp;quot;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since LCP in the field is calculated as the 75th percentile of all user visits
to a page, if a large percentage of those users had an LCP element that loaded
very quickly—for example a paragraph of text rendered with a system font—then
even if some of those users had a large, slow-loading image as their LCP
element, it might not affect that page&#39;s score if that happens to less than 25%
of visitors.&lt;/p&gt;
&lt;p&gt;Alternatively, the opposite could be true. A lab test might identify a block of
text as the LCP element because it&#39;s emulating a Moto G4 phone which has a
relatively small viewport and your page&#39;s hero image is initially rendered
off-screen. Your field data, though, may include mostly Pixel XL users with
larger screens, so for them the slow-loading hero image &lt;em&gt;is&lt;/em&gt; their LCP element.&lt;/p&gt;
&lt;h4 id=&quot;effects-of-cache-state-on-lcp&quot;&gt;Effects of cache state on LCP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#effects-of-cache-state-on-lcp&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Lab tests typically load a page with a cold cache, but when real users visit
that page they may already have some of its resources cached.&lt;/p&gt;
&lt;p&gt;The first time a user loads a page it may load slowly, but if the page has
&lt;a href=&quot;https://web.dev/http-cache/&quot;&gt;proper caching configured&lt;/a&gt;, the next time that user returns the
page might load right away.&lt;/p&gt;
&lt;p&gt;While some lab tools do support multiple runs of the same page (to simulate the
experience for returning visitors), it&#39;s not possible for a lab tool to know
what percentage of real-world visits occur from new versus returning users.&lt;/p&gt;
&lt;p&gt;Sites with well-optimized cache configurations and lots of repeat visitors may
discover that their real-world LCP is much faster than their lab data indicates.&lt;/p&gt;
&lt;h4 id=&quot;amp-or-signed-exchange-optimizations&quot;&gt;AMP or Signed Exchange optimizations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#amp-or-signed-exchange-optimizations&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Sites built with &lt;a href=&quot;https://amp.dev/&quot; rel=&quot;noopener&quot;&gt;AMP&lt;/a&gt; or that use &lt;a href=&quot;https://web.dev/signed-exchanges/&quot;&gt;Signed Exchanges
(SXG)&lt;/a&gt; can be preloaded by content aggregators like Google
Search. This can result in significantly better load performance for users
visiting your pages from those platforms.&lt;/p&gt;
&lt;p&gt;In addition to cross-origin preloading, sites themselves can
&lt;a href=&quot;https://web.dev/preload-critical-assets/&quot;&gt;preload&lt;/a&gt; content for subsequent pages on their site,
which could improve LCP for those pages as well.&lt;/p&gt;
&lt;p&gt;Lab tools do not simulate the gains seen from these optimizations, and even if
they did, they could not know what percentage of your traffic comes from
platforms like Google Search compared to other sources.&lt;/p&gt;
&lt;h4 id=&quot;effects-of-bfcache-on-lcp&quot;&gt;Effects of bfcache on LCP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#effects-of-bfcache-on-lcp&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When pages are restored from the bfcache, the load experience is near
instantaneous, and &lt;a href=&quot;https://web.dev/bfcache/#impact-on-core-web-vitals&quot;&gt;these experiences are included in your field
data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lab tests do not consider bfcache, so if your pages are
&lt;a href=&quot;https://web.dev/bfcache/#optimize-your-pages-for-bfcache&quot;&gt;bfcache-friendly&lt;/a&gt;, it will likely
result in faster LCP scores reported in the field.&lt;/p&gt;
&lt;h4 id=&quot;effects-of-user-interaction-on-lcp&quot;&gt;Effects of user interaction on LCP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#effects-of-user-interaction-on-lcp&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;LCP identifies the render time of the largest image or text block in the
viewport, but that largest element can change as the page is loaded or if new
content gets dynamically added to the viewport.&lt;/p&gt;
&lt;p&gt;In the lab, the browser will wait until the page is fully loaded before
determining what the LCP element was. But in the field, the browser will &lt;a href=&quot;https://web.dev/lcp/#when-is-largest-contentful-paint-reported&quot;&gt;stop
monitoring&lt;/a&gt; for larger elements
after the user scrolls or interacts with the page.&lt;/p&gt;
&lt;p&gt;This makes sense (and is necessary) because users typically will wait to
interact with a page until it &amp;quot;appears&amp;quot; loaded, which is exactly what the LCP
metric aims to detect. It also wouldn&#39;t make sense to consider elements added to
the viewport after a user interacts because those elements might have only been
loaded &lt;em&gt;because&lt;/em&gt; of something the user did.&lt;/p&gt;
&lt;p&gt;The implication of this, though, is that field data for a page may report faster
LCP times, depending on how users behave on that page.&lt;/p&gt;
&lt;h3 id=&quot;fid&quot;&gt;FID &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#fid&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;fid-requires-real-user-interaction&quot;&gt;FID requires real-user interaction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#fid-requires-real-user-interaction&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The FID metric measures how responsive a page is to user interactions,
&lt;em&gt;at the time when users actually chose to interact with it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The second part of that sentence is critical because lab tests, even those that
support script user behavior, cannot accurately predict when users will choose
to interact with a page, and thus cannot accurately measure FID.&lt;/p&gt;
&lt;h4 id=&quot;tbt-and-tti-do-not-consider-user-behavior&quot;&gt;TBT and TTI do not consider user behavior &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#tbt-and-tti-do-not-consider-user-behavior&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Lab metrics such as &lt;a href=&quot;https://web.dev/tbt/&quot;&gt;Total Blocking Time (TBT)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/tti/&quot;&gt;Time to Interactive
(TTI)&lt;/a&gt; are intended to help diagnose issues with FID because they
quantify how much the main thread is blocked during page load.&lt;/p&gt;
&lt;p&gt;The idea is that pages with lots of synchronous JavaScript or other intensive
rendering tasks are more likely to have a blocked main thread when the user
first interacts. However, if users wait to interact with the page until after
the JavaScript finishes executing, FID may be very low.&lt;/p&gt;
&lt;p&gt;When users will choose to interact with a page depends largely on whether or not
it &lt;em&gt;looks&lt;/em&gt; interactive, and this cannot be measured with TBT or TTI.&lt;/p&gt;
&lt;h4 id=&quot;tbt-and-tti-do-not-consider-tap-delay&quot;&gt;TBT and TTI do not consider tap delay &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#tbt-and-tti-do-not-consider-tap-delay&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;If a site is not optimized for mobile viewing, browsers will &lt;a href=&quot;https://developer.chrome.com/blog/300ms-tap-delay-gone-away/&quot; rel=&quot;noopener&quot;&gt;add a 300 ms
delay&lt;/a&gt;
after any tap before running event handlers. They do this because they need to
determine whether the user is trying to double-tap to zoom before they can fire
mouse or click events.&lt;/p&gt;
&lt;p&gt;This delay counts toward a page&#39;s FID because it contributes to the real input
latency that users experience. But since this delay is not technically a &lt;a href=&quot;https://w3c.github.io/longtasks/&quot; rel=&quot;noopener&quot;&gt;Long
Task&lt;/a&gt;, it doesn&#39;t affect a page&#39;s TBT or TTI.
This means a page may have poor FID despite having very good TBT and TTI scores.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; To avoid the tab delay issue on a page, always specify a &lt;a href=&quot;https://developer.chrome.com/blog/300ms-tap-delay-gone-away/&quot;&gt;mobile viewport&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;effects-of-cache-state-and-bfcache-on-fid&quot;&gt;Effects of cache state and bfcache on FID &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#effects-of-cache-state-and-bfcache-on-fid&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;In the same way that proper caching can improve LCP in the field, it can also
improve FID.&lt;/p&gt;
&lt;p&gt;In the real world, a user might have the JavaScript for a site already in their
cache, so processing it could &lt;a href=&quot;https://v8.dev/blog/code-caching-for-devs&quot; rel=&quot;noopener&quot;&gt;take less
time&lt;/a&gt; and result in smaller delays.&lt;/p&gt;
&lt;p&gt;The same is also true for pages restored from bfcache. In those cases the
JavaScript is restored from memory, so there could be little or no processing
time at all.&lt;/p&gt;
&lt;h3 id=&quot;cls&quot;&gt;CLS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#cls&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;effects-of-user-interaction-on-cls&quot;&gt;Effects of user interaction on CLS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#effects-of-user-interaction-on-cls&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;CLS measured in the lab only considers layout shifts that occur above
the fold and during load, but this is only a subset of what CLS actually
measures.&lt;/p&gt;
&lt;p&gt;In the field, CLS considers all &lt;a href=&quot;https://web.dev/cls/#expected-vs-unexpected-layout-shifts&quot;&gt;unexpected layout
shifts&lt;/a&gt; that occur throughout the
lifespan of the page, including content that shifts as the user scrolls or in
response to slow network requests after user interaction.&lt;/p&gt;
&lt;p&gt;For example, it&#39;s quite common for pages to lazy-load images or iframes &lt;a href=&quot;https://web.dev/optimize-cls/#images-without-dimensions&quot;&gt;without
dimensions&lt;/a&gt;, and that can cause layout
shifts when a user scrolls to those sections of the page. But those shifts may
only happen if the user scrolls down, which often won&#39;t be caught in a lab test.&lt;/p&gt;
&lt;h4 id=&quot;personalized-content&quot;&gt;Personalized content &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#personalized-content&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Personalized content—including targeted ads and A/B tests—affects what elements
are loaded on a page. It also affects how they are loaded since personalized
content is often loaded later and inserted into a page&#39;s main content, causing
layout shifts.&lt;/p&gt;
&lt;p&gt;In the lab, a page is usually loaded either without personalized content, or
with content for a generic &amp;quot;test user&amp;quot;, which may or may not trigger the shifts
real users are seeing.&lt;/p&gt;
&lt;p&gt;Since field data includes the experiences of all users, the amount (and degree)
of layout shifts experienced on any given page is very dependent on what content
is loaded.&lt;/p&gt;
&lt;h4 id=&quot;effects-of-cache-state-and-bfcache-on-cls&quot;&gt;Effects of cache state and bfcache on CLS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#effects-of-cache-state-and-bfcache-on-cls&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Two of the most common causes of layout shifts during load are images and
iframes without dimensions (as mentioned above) and &lt;a href=&quot;https://web.dev/font-best-practices/&quot;&gt;slow loading web
fonts&lt;/a&gt;, and both of these issues are more likely to
affect the experience the first time a user visits a site, when their cache is
empty.&lt;/p&gt;
&lt;p&gt;If a page&#39;s resources are cached, or if the page itself is restored from
bfcache, the browser can usually render images and fonts right away, without
waiting for them to download. This can result in lower CLS values in the field
than what a lab tool may report.&lt;/p&gt;
&lt;h2 id=&quot;what-to-do-when-the-results-are-different&quot;&gt;What to do when the results are different &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#what-to-do-when-the-results-are-different&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a general rule, if you have both field data and lab data for a given page,
field data is what you should use to prioritize your efforts. Since field data
represents what real users are experiencing, it&#39;s the most accurate way to
really understand what your users are struggling with and what needs to be
improved.&lt;/p&gt;
&lt;p&gt;On the flip side, if your field data shows good scores across the board, but
your lab data suggests there&#39;s still room for improvement, it&#39;s worth
understanding what further optimizations can be made.&lt;/p&gt;
&lt;p&gt;In addition, while field data does capture real-user experiences, &lt;em&gt;it only does
so for users who are successfully able to load your site&lt;/em&gt;. Lab data can
sometimes help identify opportunities to expand your site&#39;s reach and make it
more accessible to users with slower networks or lower-end devices.&lt;/p&gt;
&lt;p&gt;Overall, both lab data and field data are important parts of effective
performance measurement. They both have their strengths and limitations, and if
you&#39;re only using one you may be missing an opportunity to improve the
experience for your users.&lt;/p&gt;
&lt;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lab-and-field-data-differences/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/debug-performance-in-the-field/&quot;&gt;Debug performance in the Field&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/vitals-tools-workflow/&quot;&gt;A performance-focused workflow based on Google
tools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>Measure and debug performance with Google Analytics 4 and BigQuery</title>
    <link href="https://web.dev/vitals-ga4/"/>
    <updated>2021-05-18T00:00:00Z</updated>
    <id>https://web.dev/vitals-ga4/</id>
    <content type="html" mode="escaped">&lt;p&gt;Google provides a number of tools—&lt;a href=&quot;https://support.google.com/webmasters/answer/9205520&quot; rel=&quot;noopener&quot;&gt;Search
Console&lt;/a&gt;, &lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed
Insights&lt;/a&gt; (PSI), and
the &lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience
Report&lt;/a&gt;
(CrUX)—that let developers see how their sites perform against the &lt;a href=&quot;https://web.dev/vitals/#core-web-vitals&quot;&gt;Core Web
Vitals&lt;/a&gt; metrics for their real users &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;in the
field&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These tools are great in that they give you a high-level view of your site&#39;s
real-user performance, and they require absolutely no setup to start using.&lt;/p&gt;
&lt;p&gt;However, there are a few critical reasons why you don&#39;t want to rely on these
tools alone to measure your site&#39;s performance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CrUX-based tools report data by either monthly or previous 28-day periods.
This means you&#39;ll have to wait a long time after making any changes before you
can see the results.&lt;/li&gt;
&lt;li&gt;CrUX-based tools can only be segment by a limited number of dimensions, such as
country, connection type, and device category (desktop or mobile). You can&#39;t
slice the data by dimensions specific to your business (for example: engaged
users, users in a particular experiment group, etc.).&lt;/li&gt;
&lt;li&gt;CrUX-based tools can tell you &lt;em&gt;what&lt;/em&gt; your performance is, but they can&#39;t tell
you &lt;em&gt;why&lt;/em&gt;. With analytics tools you can send additional data to help you track
down and debug issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For these reasons, we recommend all site owners monitor Core Web Vitals metrics
using their existing analytics tool. This post explains how you can use free
tools offered by Google to do just that.&lt;/p&gt;
&lt;p&gt;Once you have everything set up, you&#39;ll be able to create dashboards like these:&lt;/p&gt;
&lt;img alt=&quot;Web Vitals Connector report screenshot&quot; decoding=&quot;async&quot; height=&quot;452&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/Qxzm5QpwjaVAsqi28g5J.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;img alt=&quot;Web Vitals Connector report screenshot&quot; decoding=&quot;async&quot; height=&quot;474&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/eJcnoFjJhPVeZCkS1gsG.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;And If you want a visual overview of all the steps outlined here, check out &lt;a href=&quot;https://www.youtube.com/watch?v=xg47r3Y6K8I&quot; rel=&quot;noopener&quot;&gt;our talk from Google I/O &#39;21&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;xg47r3Y6K8I&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;h2 id=&quot;measure&quot;&gt;Measure &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#measure&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Measuring performance has always been possible with Google Analytics using
&lt;a href=&quot;https://support.google.com/analytics/answer/2709829&quot; rel=&quot;noopener&quot;&gt;custom metrics&lt;/a&gt;, but there
are a few new features in &lt;a href=&quot;https://support.google.com/analytics/answer/10089681&quot; rel=&quot;noopener&quot;&gt;Google Analytics
4&lt;/a&gt; (GA4) that developers
in particular should be excited about.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zero-config, &lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/ga4/events#custom_events&quot; rel=&quot;noopener&quot;&gt;custom
event&lt;/a&gt;
parameters&lt;/li&gt;
&lt;li&gt;Free &lt;a href=&quot;https://support.google.com/analytics/answer/9358801&quot; rel=&quot;noopener&quot;&gt;BigQuery export&lt;/a&gt;,
so you can query your data using SQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the Google Analytics web interface does have powerful analysis tools, it&#39;s
hard to beat the power and flexibility of raw event data access using a query
language you likely already know.&lt;/p&gt;
&lt;p&gt;To get started measuring Core Web Vitals using Google Analytics 4 and BigQuery,
you need to do three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/analytics/answer/10089681#start&quot; rel=&quot;noopener&quot;&gt;Create a Google Analytics 4
property&lt;/a&gt; and a
&lt;a href=&quot;https://cloud.google.com/bigquery&quot; rel=&quot;noopener&quot;&gt;BigQuery project&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Enable &lt;a href=&quot;https://support.google.com/analytics/answer/9358801&quot; rel=&quot;noopener&quot;&gt;BigQuery export&lt;/a&gt;
in your Google Analytics property config, so all data you receive will be
automatically populated in your BigQuery project tables.&lt;/li&gt;
&lt;li&gt;Add the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;web-vitals&lt;/a&gt; JavaScript
library to your site, so you can measure the Core Web Vitals metrics and
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#using-gtagjs-google-analytics-4&quot; rel=&quot;noopener&quot;&gt;send the data to Google Analytics
4&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;analyze&quot;&gt;Analyze &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#analyze&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once you&#39;re all set up, you should see event data populating in the BigQuery
interface, and you should be able to query the data like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;my_project_id.analytics_XXXXX.events_*&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; event_name &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FID&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Here&#39;s a preview of the results from that query:&lt;/p&gt;
&lt;img alt=&quot;Web Vitals event data in BigQuery&quot; decoding=&quot;async&quot; height=&quot;418&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CfzjCEhryDJbVgrWSKV6.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;query-web-vitals-data&quot;&gt;Query Web Vitals data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#query-web-vitals-data&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before you start querying your Web Vitals event data, it&#39;s important to
understand how the data is aggregated.&lt;/p&gt;
&lt;p&gt;The most important thing to understand is that, in some cases, &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#:~:text=In%20other%20cases%2C%20a%20metric%20callback%20may%20be%20called%20more%20than%20once%3A&quot; rel=&quot;noopener&quot;&gt;multiple events
may be
received&lt;/a&gt;
for the same metric, on the same page. This can happen if the metric value
changes and an updated value is reported (a common occurrence with CLS).&lt;/p&gt;
&lt;p&gt;For Web Vitals events, the last value sent is always the most accurate one, so
before performing any analysis, it&#39;s important to filter for just those values.
The code snippet provided by the web-vitals JavaScript library to send data to
Google Analytics 4 includes sending a unique ID per metric, so you can use the
following query to limit your results to just the last-received value for each
metric ID:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Subquery all Web Vitals events from the last 28 days&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; web_vitals_events &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; event_name &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXCEPT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; is_last_received_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ROW_NUMBER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; is_last_received_value&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;bigquery_project_id.analytics_XXXXX.events_*&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; event_name &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FID&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;&lt;br /&gt;      _TABLE_SUFFIX &lt;span class=&quot;token operator&quot;&gt;BETWEEN&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; is_last_received_value&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Note that all other queries referenced in this post will start with this
subquery.&lt;/p&gt;
&lt;p&gt;The next few sections show a few examples of common Web Vitals queries you might
want to run.&lt;/p&gt;
&lt;h3 id=&quot;example-queries&quot;&gt;Example queries &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#example-queries&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;lcp,-fid,-and-cls-at-the-75percent-percentile-p75-across-the-whole-site&quot;&gt;LCP, FID, and CLS at the 75% percentile (p75) across the whole site &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#lcp,-fid,-and-cls-at-the-75percent-percentile-p75-across-the-whole-site&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;div&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Subquery all Web Vitals events from the last 28 days&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; web_vitals_events &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; event_name &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXCEPT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; is_last_received_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ROW_NUMBER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; is_last_received_value&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;bigquery_project_id.analytics_XXXXX.events_*&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; event_name &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FID&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;&lt;br /&gt;      _TABLE_SUFFIX &lt;span class=&quot;token operator&quot;&gt;BETWEEN&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; is_last_received_value&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Main query logic&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;  metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  APPROX_QUANTILES&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;OFFSET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; p75&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; count&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;    metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;ROUND&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;metric_value&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; web_vitals_events&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;all-individual-lcp-values-from-highest-to-lowest&quot;&gt;All individual LCP values from highest to lowest &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#all-individual-lcp-values-from-highest-to-lowest&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;div&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Subquery all Web Vitals events from the last 28 days&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; web_vitals_events &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; event_name &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXCEPT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; is_last_received_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ROW_NUMBER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; is_last_received_value&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;bigquery_project_id.analytics_XXXXX.events_*&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; event_name &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FID&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;&lt;br /&gt;      _TABLE_SUFFIX &lt;span class=&quot;token operator&quot;&gt;BETWEEN&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; is_last_received_value&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Main query logic&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;ROUND&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;metric_value&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; web_vitals_events&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; metric_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; metric_value &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;lcp-scores-p75-of-the-10-most-popular-pages&quot;&gt;LCP scores (p75) of the 10 most popular pages &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#lcp-scores-p75-of-the-10-most-popular-pages&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;div&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Subquery all Web Vitals events from the last 28 days&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; web_vitals_events &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; event_name &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXCEPT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; is_last_received_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ROW_NUMBER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; is_last_received_value&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;bigquery_project_id.analytics_XXXXX.events_*&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; event_name &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FID&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;&lt;br /&gt;      _TABLE_SUFFIX &lt;span class=&quot;token operator&quot;&gt;BETWEEN&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; is_last_received_value&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Main query logic&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;  page_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  APPROX_QUANTILES&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;OFFSET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; LCP&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; count&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;    REGEXP_SUBSTR&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;page_location&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token string&quot;&gt;&#39;\.com(\/[^?]*)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; page_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;ROUND&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;metric_value&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; web_vitals_events&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; metric_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; count &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;top-10-pages-with-the-worst-cls-p75&quot;&gt;Top 10 pages with the worst CLS (p75) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#top-10-pages-with-the-worst-cls-p75&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;div&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Subquery all Web Vitals events from the last 28 days&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; web_vitals_events &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; event_name &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXCEPT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; is_last_received_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ROW_NUMBER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; is_last_received_value&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;bigquery_project_id.analytics_XXXXX.events_*&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; event_name &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FID&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;&lt;br /&gt;      _TABLE_SUFFIX &lt;span class=&quot;token operator&quot;&gt;BETWEEN&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; is_last_received_value&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Main query logic&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;  page_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  APPROX_QUANTILES&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;OFFSET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; CLS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; count&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;    REGEXP_SUBSTR&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;page_location&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token string&quot;&gt;&#39;\.com(\/[^?]*)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; page_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;ROUND&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;metric_value&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; web_vitals_events&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; metric_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;HAVING&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Limit to relatively popular pages&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; CLS &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;debug&quot;&gt;Debug &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#debug&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The above queries show how to query the Web Vitals metric data, which is helpful
for understanding your current performance and how it&#39;s trending over time. But
what can you do if your performance is worse than expected but you&#39;re not sure
why?&lt;/p&gt;
&lt;p&gt;Knowing &lt;em&gt;what&lt;/em&gt; your scores are is not helpful if you&#39;re not able to take action
and fix the problems.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/debug-performance-in-the-field/&quot;&gt;Debug performance in the field&lt;/a&gt; explains how
you can send additional debug information with your analytics data. If you
follow the instructions detailed in that post, you should see that debug
information appear in BigQuery as well.&lt;/p&gt;
&lt;p&gt;The following queries show how to use the &lt;code&gt;debug_target&lt;/code&gt; event parameter to help
identify the root cause of performance issues.&lt;/p&gt;
&lt;h3 id=&quot;example-queries-2&quot;&gt;Example queries &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#example-queries-2&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;top-elements-contributing-to-cls&quot;&gt;Top elements contributing to CLS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#top-elements-contributing-to-cls&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;debug_target&lt;/code&gt; is a CSS selector string that corresponds to the element on
the page that is most relevant to the metric value.&lt;/p&gt;
&lt;p&gt;With CLS, the &lt;code&gt;debug_target&lt;/code&gt; represents the largest element from the largest
layout shift that contributed to the CLS value. If no elements shifted then the
&lt;code&gt;debug_target&lt;/code&gt; value will be &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The following query will list pages from worst to best by their CLS at the 75th
percentile, grouped by &lt;code&gt;debug_target&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Subquery all Web Vitals events from the last 28 days&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; web_vitals_events &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; event_name &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXCEPT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; is_last_received_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ROW_NUMBER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; is_last_received_value&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;bigquery_project_id.analytics_XXXXX.events_*&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; event_name &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FID&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;&lt;br /&gt;      _TABLE_SUFFIX &lt;span class=&quot;token operator&quot;&gt;BETWEEN&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; is_last_received_value&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Main query logic&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;  page_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  debug_target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  APPROX_QUANTILES&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;OFFSET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; CLS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; count&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;    REGEXP_SUBSTR&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;page_location&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token string&quot;&gt;&#39;\.com(\/[^?]*)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; page_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;debug_target&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; debug_target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;ROUND&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;metric_value&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; web_vitals_events&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; metric_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;HAVING&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Limit to relatively popular pages&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; CLS &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;img alt=&quot;Query result for top elements contributing to CLS&quot; decoding=&quot;async&quot; height=&quot;445&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/0rgPwLXOplwT706jvmuy.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Knowing what elements on the page are shifting should make it much easier to
identify and fix the root cause of the problem.&lt;/p&gt;
&lt;p&gt;Keep in mind that the elements reported here might not be the same elements that
you see shifting when you&#39;re debugging your pages locally, which is why it&#39;s so
important to capture this data in the first place. It&#39;s very hard to fix things
that you don&#39;t realize are problems!&lt;/p&gt;
&lt;h4 id=&quot;debug-other-metrics&quot;&gt;Debug other metrics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#debug-other-metrics&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The above query shows the results for the CLS metric, but the exact same
technique can be used to report on the debug targets for LCP and FID. Just
replace the where clause with the relevant metric to debug:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; metric_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; metric_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;/ins&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Again, you can refer to &lt;a href=&quot;https://web.dev/debug-performance-in-the-field/&quot;&gt;Debug performance in the
field&lt;/a&gt; for instructions on how to collect and
send debug information for each of the Core Web Vitals metrics.&lt;/p&gt;
&lt;h2 id=&quot;visualize&quot;&gt;Visualize &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#visualize&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It can be challenging to get insights just by looking at the query results
alone. For example, the following query lists daily 75th percentile values for
LCP in the dataset.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Subquery all Web Vitals events from the last 28 days&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; web_vitals_events &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; event_name &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXCEPT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; is_last_received_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ROW_NUMBER&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OVER&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DESC&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; is_last_received_value&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;bigquery_project_id.analytics_XXXXX.events_*&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; event_name &lt;span class=&quot;token operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FID&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;&lt;br /&gt;      _TABLE_SUFFIX &lt;span class=&quot;token operator&quot;&gt;BETWEEN&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;28&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; FORMAT_DATE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; DATE_SUB&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;CURRENT_DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; is_last_received_value&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Main query logic&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;  event_date&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  APPROX_QUANTILES&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ROUND&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;OFFSET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; p75&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;      event_date&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;ROUND&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; metric_value&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; web_vitals_events&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt;&lt;br /&gt;      metric_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; event_date&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;From these query results, it&#39;s difficult to identify trends or outliers just by
looking at the data.&lt;/p&gt;
&lt;img alt=&quot;Daily metric value query results&quot; decoding=&quot;async&quot; height=&quot;386&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/uDQWTwIEelkSTu1ExyRB.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
&lt;p&gt;In such cases, visualizing the data can help you derive insights more quickly.&lt;/p&gt;
&lt;h3 id=&quot;visualize-query-results-in-looker-studio&quot;&gt;Visualize query results in Looker Studio &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#visualize-query-results-in-looker-studio&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;BigQuery provides a quick way to visualize any query results through Data
Studio. &lt;a href=&quot;https://lookerstudio.google.com/&quot; rel=&quot;noopener&quot;&gt;Looker Studio&lt;/a&gt; is a data visualization
and dashboarding tool that is free to use. To visualize your query results,
after running your query in the BigQuery UI, click the &lt;em&gt;Explore Data button&lt;/em&gt; and
select &lt;em&gt;Explore with Looker Studio&lt;/em&gt;.&lt;/p&gt;
&lt;img alt=&quot;Explore with Looker Studio option in BigQuery&quot; decoding=&quot;async&quot; height=&quot;191&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/CFQ8JK7UlZsxUbWZXCO0.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;This will create a direct link from BigQuery into Looker Studio in the explore
view. In this view, you can select the fields you want to visualize, choose
chart types, setup filters, and create ad hoc charts for quick visual analysis.
From the above query results, you can create this line chart to see the trend of
LCP values over time:&lt;/p&gt;
&lt;img alt=&quot;Line chart of daily LCP values in Looker Studio&quot; decoding=&quot;async&quot; height=&quot;413&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/mhTsDWbzpveguHadWhS0.PNG?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;With this direct link between BigQuery and Looker Studio, you can create quick
charts from any of your queries and do visual analysis. However, if you want to
do additional analysis, you might want to look at several charts in an
interactive dashboard to get a more holistic view or to be able to drill down
into the data. Having a handy dashboard means you don&#39;t have to write queries
and generate charts manually every time you want to analyze your metrics.&lt;/p&gt;
&lt;p&gt;You can create a dashboard in Looker Studio using the &lt;a href=&quot;https://support.google.com/datastudio/answer/6370296&quot; rel=&quot;noopener&quot;&gt;native BigQuery
connector&lt;/a&gt;. To do so,
navigate to &lt;a href=&quot;https://datastudio.google.com/&quot; rel=&quot;noopener&quot;&gt;datastudio.google.com&lt;/a&gt;, create a new
data source, select the BigQuery connector, and choose the dataset you want to
work with:&lt;/p&gt;
&lt;img alt=&quot;Using the BigQuery native connector in Looker Studio&quot; decoding=&quot;async&quot; height=&quot;302&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/dTS0rVPXlWfkJ67VZYPxGwaAj7j1/NoLpS3R5OnX52QEu6rnN.PNG?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;materialize-web-vitals-data&quot;&gt;Materialize Web Vitals data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#materialize-web-vitals-data&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When creating dashboards of the Web Vitals event data as described above, it&#39;s
not efficient to use the Google Analytics 4 export dataset directly. Due to the
structure of the GA4 data and the preprocessing required for the Web Vitals
metrics, parts of your query will end up running multiple times. This creates
two problems: dashboard performance and BigQuery costs.&lt;/p&gt;
&lt;p&gt;You can use the &lt;a href=&quot;https://cloud.google.com/bigquery/docs/sandbox&quot; rel=&quot;noopener&quot;&gt;BigQuery sandbox
mode&lt;/a&gt; for free. With BigQuery&#39;s
&lt;a href=&quot;https://cloud.google.com/bigquery/pricing#free-tier&quot; rel=&quot;noopener&quot;&gt;free usage tier&lt;/a&gt;, the
first 1 TB of query data processed per month is free. For the analysis methods
discussed in this post, unless you are using a significantly large dataset or
are heavily querying the dataset regularly, you should be able to stay within
this free limit every month. But if you have a high traffic website and want to
regularly monitor different metrics using a fast interactive dashboard, we
suggest preprocessing and materializing your web vitals data while making use of
BigQuery efficiency features like partitioning, clustering, and caching.&lt;/p&gt;
&lt;p&gt;The following script will preprocess your BigQuery data (source table) and
create a materialized table (target table). When using this query for your own
dataset, you might also want to define a date range for the source table to
lower the amount of data processed.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Materialize Web Vitals metrics from GA4 event export data&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Replace target table name&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; bigquery_project_id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ga4_demo_dev&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;web_vitals_summary&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  CLUSTER &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; metric_name&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;  ga_session_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; e &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;event_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;first_visit&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;New user&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;Returning user&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; user_type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MAX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session_engaged&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Engaged&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Not engaged&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; session_engagement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  evt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXCEPT&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;session_engaged&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  event_name &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; metric_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  FORMAT_TIMESTAMP&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;%Y%m%d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event_timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; event_date&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;      ga_session_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      ARRAY_AGG&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;custom_event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; events&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;          ga_session_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          STRUCT&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            country&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            device_category&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            device_os&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            traffic_medium&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            traffic_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            traffic_source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            page_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            debug_target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            event_timestamp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            event_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            metric_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metric_value &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metric_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            user_pseudo_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            session_engaged&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            session_revenue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; custom_event&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ga_session_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; ga_session_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; metric_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              ANY_VALUE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;device&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;category&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; device_category&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              ANY_VALUE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;device&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;operating_system&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; device_os&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              ANY_VALUE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;traffic_source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;medium&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; traffic_medium&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              ANY_VALUE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;traffic_source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; traffic_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              ANY_VALUE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;traffic_source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; traffic_source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              ANY_VALUE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                REGEXP_SUBSTR&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;page_location&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                  r&lt;span class=&quot;token string&quot;&gt;&#39;^[^?]+&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; page_path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              ANY_VALUE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;debug_target&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; debug_target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              ANY_VALUE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_pseudo_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; user_pseudo_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              ANY_VALUE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;geo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;country&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; country&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              ANY_VALUE&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; event_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token function&quot;&gt;SUM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ecommerce&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;purchase_revenue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; session_revenue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token function&quot;&gt;MAX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                      &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; CAST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string_value &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;NUMERIC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;session_engaged&#39;&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; session_engaged&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              TIMESTAMP_MICROS&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;MAX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; event_timestamp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token function&quot;&gt;MAX&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;double_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;int_value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;metric_value&#39;&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; metric_value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token comment&quot;&gt;# Replace source table name&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;bigquery_project_id.analytics_XXXXX.events_*&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt;&lt;br /&gt;              event_name &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FID&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;first_visit&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;purchase&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt;&lt;br /&gt;      ga_session_id &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; ga_session_id&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;CROSS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; UNNEST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; evt&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; evt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;event_name &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;first_visit&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;purchase&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This materialized dataset has several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The data structure is flattened and easier to query.&lt;/li&gt;
&lt;li&gt;It retains only the Web Vitals events from the original GA4 dataset.&lt;/li&gt;
&lt;li&gt;Session ID, user type (new vs returning), and session engagement information
is directly available in columns.&lt;/li&gt;
&lt;li&gt;The table is
&lt;a href=&quot;https://cloud.google.com/bigquery/docs/partitioned-tables&quot; rel=&quot;noopener&quot;&gt;partitioned&lt;/a&gt; by
date and &lt;a href=&quot;https://cloud.google.com/bigquery/docs/clustered-tables&quot; rel=&quot;noopener&quot;&gt;clustered&lt;/a&gt;
by metric name. This usually reduces the amount of data processed for each
query.&lt;/li&gt;
&lt;li&gt;Since you don&#39;t need to use wildcards to query this table, query results can
get cached for upto 24 hours. This reduces costs from repeating the same
query.&lt;/li&gt;
&lt;li&gt;If you use the BigQuery BI engine, you can run &lt;a href=&quot;https://cloud.google.com/bi-engine/docs/optimized-sql&quot; rel=&quot;noopener&quot;&gt;optimized SQL functions and
operators&lt;/a&gt; on this
table.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can directly query this materialized table from within BigQuery UI or use it
in Looker Studio using the BigQuery connector.&lt;/p&gt;
&lt;h3 id=&quot;using-the-web-vitals-connector&quot;&gt;Using the Web Vitals Connector &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#using-the-web-vitals-connector&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Since making a dashboard from scratch is time consuming, we developed a packaged
solution that will create a template dashboard for you. First make sure that you
have materialized your Web Vitals table using the above query. Then access the
Web Vitals connector for Looker Studio using this link:
&lt;a href=&quot;https://goo.gle/web-vitals-connector&quot; rel=&quot;noopener&quot;&gt;goo.gle/web-vitals-connector&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After providing a one time authorization, you should see the following
configuration screen:&lt;/p&gt;
&lt;img alt=&quot;Web Vitals Connector authorization screen&quot; decoding=&quot;async&quot; height=&quot;502&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/SHtnSZLE4iRz5r1Pf3K8.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Provide the materialized BigQuery table ID (i.e. the target table) and your
BigQuery billing project ID. After clicking connect, Looker Studio will create a
new templated dashboard and associate your data with it.  You can edit, modify,
and share the dashboard as you like. If you create a dashboard once, you don&#39;t
have to visit the connector link again, unless you want to create multiple
dashboards from different datasets.&lt;/p&gt;
&lt;h3 id=&quot;navigate-the-dashboard&quot;&gt;Navigate the dashboard &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#navigate-the-dashboard&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As you navigate the dashboard, you can see the daily trends of the Web Vitals
metrics and some usage information for your website like users and sessions, in
the &lt;em&gt;Summary&lt;/em&gt; tab.&lt;/p&gt;
&lt;p&gt;In the &lt;em&gt;User Analysis&lt;/em&gt; tab, you can select a metric and then get a breakdown of
the metrics percentile as well as user count by different usage and business
metrics.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Page Path Analysis&lt;/em&gt; tab will help you to identify problem areas on your
website. Here, You can pick a metric to see the overview. But you also see the
scatter-map of all the page paths with the percentile value on y axis and record
count on x axis. The scatter map can help to identify pages which have lower
than expected metric values. Once you select the pages either via the scatter
chart of the &lt;em&gt;Page path&lt;/em&gt; table, you can further drill down the problem area by
viewing the &lt;em&gt;Debug Target&lt;/em&gt; table.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Revenue Analysis&lt;/em&gt; tab is an example of how you can monitor your business
and performance metrics in the same place. This section plots all sessions where
the user made a purchase. You can compare the revenue earned vs user experience
during a specific session .&lt;/p&gt;
&lt;h3 id=&quot;advanced-usage&quot;&gt;Advanced usage &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#advanced-usage&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As you become more familiar with the dataset, you can edit the dashboard and add
your own charts for richer and targeted analysis. To make the dashboard more
useful, you can take the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setup scheduled query in BigQuery to get updated data. The materialization
query we ran above only takes a snapshot of your data at that moment. If you
want to keep your dashboard updated with new data, you can run a scheduled
query that will run every day and append your materialized table with the new
data.&lt;/li&gt;
&lt;li&gt;Join first party data (e.g. CRM) for business insights. In the materialized
table, you can add &lt;code&gt;user_id&lt;/code&gt; as a separate column. This will let you join your
first party data. If your first party data is not already in BigQuery, you can
either &lt;a href=&quot;https://cloud.google.com/bigquery/docs/loading-data&quot; rel=&quot;noopener&quot;&gt;load the data&lt;/a&gt; or
use a &lt;a href=&quot;https://cloud.google.com/bigquery/external-data-sources&quot; rel=&quot;noopener&quot;&gt;federated data
source&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Report your site or app version as a parameter in the data you send to Google
Analytics and add it as a column in the materialized table. Then you can add
that version data as a dimension in your charts to make it easier to see
version changes affect performance.&lt;/li&gt;
&lt;li&gt;If you are expecting significantly heavy usage of the dataset through direct
query or the dashboard, you can try using the paid version of &lt;a href=&quot;https://cloud.google.com/bi-engine/docs/introduction&quot; rel=&quot;noopener&quot;&gt;BigQuery BI
Engine&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-ga4/#summary&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This post covered the basics of how to use Google Analytics 4 and BigQuery to
measure and debug performance with real-user data collected in the field. It
also explained how to build automated reports and dashboards using Looker Studio
and the &lt;a href=&quot;https://goo.gle/web-vitals-connector&quot; rel=&quot;noopener&quot;&gt;Web Vitals Connector&lt;/a&gt; to make
visualizing the data as easy as possible.&lt;/p&gt;
&lt;p&gt;Some key takeaways from this post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Measuring performance with real user data is critical for understanding,
debugging, and optimizing your site.&lt;/li&gt;
&lt;li&gt;You can get deeper insights when your performance metrics and your business
metrics are in the same system. Google Analytics and BigQuery make this
possible.&lt;/li&gt;
&lt;li&gt;BigQuery export of raw Google Analytics data gives you unlimited potential for
in-depth, custom analysis using a query language you likely already know.&lt;/li&gt;
&lt;li&gt;Google has a number of APIs and visualization tools like Looker Studio that give
you the freedom to build your reports exactly the way you want them to be
built.&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author><author>
      <name>Minhaz Kazi</name>
    </author>
  </entry>
  
  <entry>
    <title>Debug performance in the field</title>
    <link href="https://web.dev/debug-performance-in-the-field/"/>
    <updated>2021-04-01T00:00:00Z</updated>
    <id>https://web.dev/debug-performance-in-the-field/</id>
    <content type="html" mode="escaped">&lt;p&gt;Google currently provides two categories of tools to measure and debug performance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lab tools:&lt;/strong&gt; Tools such as Lighthouse, where your page is loaded in a
simulated environment that can mimic various conditions (for example, a slow
network and a low-end mobile device).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Field tools:&lt;/strong&gt; Tools such as &lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience
Report&lt;/a&gt;
(CrUX), which is based on aggregate, real-user data from Chrome. (Note that the
field data reported by tools such as &lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed
Insights&lt;/a&gt; and &lt;a href=&quot;https://support.google.com/webmasters/answer/9205520&quot; rel=&quot;noopener&quot;&gt;Search
Console&lt;/a&gt; is sourced from
CrUX data.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While field tools offer more accurate data—data which actually represents what
real users experience—lab tools are often better at helping you identify and fix
issues.&lt;/p&gt;
&lt;p&gt;CrUX data is more representative of your page&#39;s real performance, but knowing
your CrUX scores is unlikely to help you figure out &lt;em&gt;how&lt;/em&gt; to improve your
performance.&lt;/p&gt;
&lt;p&gt;Lighthouse, on the other hand, will identify issues and make specific
suggestions for how to improve. However, Lighthouse will only make suggestions
for performance issues it discovers at page load time. It does not detect issues
that only manifest as a result of user interaction such as scrolling or clicking
buttons on the page.&lt;/p&gt;
&lt;p&gt;This raises an important question: &lt;strong&gt;how can you capture debug information for
Core Web Vitals or other performance metrics from real users in the field?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This post will explain in detail what APIs you can use to collect additional
debugging information for each of the current Core Web Vitals metrics and give
you ideas for how to capture this data in your existing analytics tool.&lt;/p&gt;
&lt;h2 id=&quot;apis-for-attribution-and-debugging&quot;&gt;APIs for attribution and debugging &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#apis-for-attribution-and-debugging&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;cls&quot;&gt;CLS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#cls&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Of all the Core Web Vitals metrics, &lt;a href=&quot;https://web.dev/cls/&quot;&gt;CLS&lt;/a&gt; is perhaps the one for which
collecting debug information in the field is the most important. CLS is measured
throughout the entire lifespan of the page, so the way a user interacts with the
page—how far they scroll, what they click on, and so on—can have a significant
impact on whether there are layout shifts and which elements are shifting.&lt;/p&gt;
&lt;p&gt;Consider the following report from PageSpeed Insights for the URL:
&lt;a href=&quot;https://web.dev/measure/&quot;&gt;web.dev/measure&lt;/a&gt;&lt;/p&gt;
&lt;img alt=&quot;A PageSpeed Insights Report with different CLS values&quot; decoding=&quot;async&quot; height=&quot;587&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/nZjd6rXrOgW5VUsm5fyx.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;The value reported for CLS from the lab (Lighthouse) compared to the CLS from
the field (CrUX data) are quite different, and this makes sense if you consider
that the &lt;a href=&quot;https://web.dev/measure/&quot;&gt;web.dev/measure&lt;/a&gt; page has a lot of interactive content that
is not being used when tested in Lighthouse.&lt;/p&gt;
&lt;p&gt;But even if you understand that user interaction affects field data, you still
need to know what elements on the page are shifting to result in a score of 0.45
at the 75th percentile.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/debug-layout-shifts/#layoutshiftattribution&quot;&gt;LayoutShiftAttribution&lt;/a&gt;
interface makes that possible.&lt;/p&gt;
&lt;h4 id=&quot;get-layout-shift-attribution&quot;&gt;Get layout shift attribution &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#get-layout-shift-attribution&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/debug-layout-shifts/#layoutshiftattribution&quot;&gt;LayoutShiftAttribution&lt;/a&gt;
interface is exposed on each &lt;code&gt;layout-shift&lt;/code&gt; entry that &lt;a href=&quot;https://wicg.github.io/layout-instability&quot; rel=&quot;noopener&quot;&gt;Layout Instability
API&lt;/a&gt; emits.&lt;/p&gt;
&lt;p&gt;For a detailed explanation of both of these interfaces, see &lt;a href=&quot;https://web.dev/debug-layout-shifts/#layoutshiftattribution&quot;&gt;Debug layout
shifts&lt;/a&gt;. For the purposes of
this post, the main thing you need to know is that, as a developer, you are able
to observe every layout shift that happens on the page as well as what elements
are shifting.&lt;/p&gt;
&lt;p&gt;Here&#39;s some example code that logs each layout shift as well as the elements
that shifted:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; startTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sources&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Log the shift amount and other entry info.&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Layout shift:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; startTime&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sources&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; curRect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; prevRect&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; sources&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Log the elements that shifted.&lt;/span&gt;&lt;br /&gt;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;  Shift source:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;curRect&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; prevRect&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;layout-shift&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;It&#39;s probably not practical to measure and send data to your analytics tool for
every single layout shift that occurs; however, by monitoring all shifts, you
can keep track of the worst shifts and just report information about those.&lt;/p&gt;
&lt;p&gt;The goal isn&#39;t to identify and fix every single layout shift that occurs for
every user; the goal is to identify the shifts that affect the largest number of
users and thus contribute the most to your page&#39;s CLS at the 75th percentile.&lt;/p&gt;
&lt;p&gt;Also, you don&#39;t need to compute the largest source element every time there&#39;s a
shift, you only need to do so when you&#39;re ready to send the CLS value to your
analytics tool.&lt;/p&gt;
&lt;p&gt;The following code takes a list of &lt;code&gt;layout-shift&lt;/code&gt; entries that have contributed
to CLS and returns the largest source element from the largest shift:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getCLSDebugTarget&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; largestEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;largestEntry &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; largestEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sources &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; largestEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sources&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; largestSource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; largestEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sources&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;node &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;previousRect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;previousRect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;          b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;previousRect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;previousRect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;largestSource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; largestSource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Once you&#39;ve identified the largest element contributing to the largest shift,
you can report that to your analytics tool.&lt;/p&gt;
&lt;p&gt;The element contributing the most to CLS for a given page will likely vary from
user to user, but if you aggregate those elements across all users, you&#39;ll be
able to generate a list of shifting elements affecting the most number of users.&lt;/p&gt;
&lt;p&gt;After you&#39;ve identified and fixed the root cause of the shifts for those
elements, your analytics code will start reporting smaller shifts as the &amp;quot;worst&amp;quot;
shifts for your pages. Eventually, all reported shifts will be small enough that
your pages are well within &lt;a href=&quot;https://web.dev/cls/#what-is-a-good-cls-score&quot;&gt;the &amp;quot;good&amp;quot; threshold of
0.1&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Some other metadata that may be useful to capture along with the largest shift
source element is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The time of the largest shift&lt;/li&gt;
&lt;li&gt;The URL path at the time of the largest shift (for sites that dynamically
update the URL, such as Single Page Applications).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;lcp&quot;&gt;LCP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#lcp&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To debug LCP in the field, the primary information you need is which particular
element was the largest element (the LCP candidate element) for that particular
page load.&lt;/p&gt;
&lt;p&gt;Note that it&#39;s entirely possible—in fact, it&#39;s quite common—that the LCP
candidate element will be different from user to user, even for the exact same
page.&lt;/p&gt;
&lt;p&gt;This can happen for several reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User devices have different screen resolutions, which results in different
page layouts and thus different elements being visible within the viewport.&lt;/li&gt;
&lt;li&gt;Users don&#39;t always load pages scrolled to the very top. Oftentimes links will
contain &lt;a href=&quot;https://web.dev/text-fragments/#fragment-identifiers&quot;&gt;fragment identifiers&lt;/a&gt; or even
&lt;a href=&quot;https://web.dev/text-fragments/#text-fragments&quot;&gt;text fragments&lt;/a&gt;, which means it&#39;s possible
for your pages to be loaded and displayed at any scroll position on the page.&lt;/li&gt;
&lt;li&gt;Content may be personalized for the current user, so the LCP candidate element
could vary wildly from user to user.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means you cannot make assumptions about which element or set of elements
will be the most common LCP candidate element for a particular page. You have to
measure it based on real-user behavior.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; For more details on these differences, see: &lt;a href=&quot;https://web.dev/lab-and-field-data-differences/&quot;&gt;Why lab and field data can be different (and what to do about it)&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;identify-the-lcp-candidate-element&quot;&gt;Identify the LCP candidate element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#identify-the-lcp-candidate-element&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To determine the LCP candidate element in JavaScript you can use the &lt;a href=&quot;https://wicg.github.io/largest-contentful-paint/&quot; rel=&quot;noopener&quot;&gt;Largest
Contentful Paint API&lt;/a&gt;, the
same API you use to determine the LCP time value.&lt;/p&gt;
&lt;p&gt;When observing &lt;code&gt;largest-contentful-paint&lt;/code&gt; entries, you can determine the
current LCP candidate element by looking at the &lt;code&gt;element&lt;/code&gt; property of the last entry:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lastEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entries&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;entries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;LCP element:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lastEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;largest-contentful-paint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; As explained in the &lt;a href=&quot;https://web.dev/lcp/&quot;&gt;LCP metric documentation&lt;/a&gt;, the LCP candidate element can change through the page load, so more work is required to identify the &amp;quot;final&amp;quot; LCP candidate element. The easiest way to identify and measure the &amp;quot;final&amp;quot; LCP candidate element is to use the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/&quot;&gt;web-vitals&lt;/a&gt; JavaScript library, as shown in the &lt;a href=&quot;https://web.dev/debug-performance-in-the-field/#usage-with-the-web-vitals-javascript-library&quot;&gt;example below&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Once you know the LCP candidate element, you can send it to your analytics tool
along with the metric value. As with CLS, this will help you identify which
elements are most important to optimize first.&lt;/p&gt;
&lt;p&gt;In addition to the LCP candidate element, it may also be useful to measure the
&lt;a href=&quot;https://web.dev/optimize-lcp/#optimal-sub-part-times&quot;&gt;LCP sub-part times&lt;/a&gt;, which can be useful
in determining what specific optimization steps are relevant for your site.&lt;/p&gt;
&lt;h3 id=&quot;fid&quot;&gt;FID &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#fid&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To debug FID in the field, it&#39;s important to remember that FID measures &lt;a href=&quot;https://web.dev/fid/#fid-in-detail&quot;&gt;only
the delay portion&lt;/a&gt; of the overall first input event
latency. That means that what the user interacted with is not really as
important as what else was happening on the main thread at the time they
interacted.&lt;/p&gt;
&lt;p&gt;For example, many JavaScript applications that support server-side rendering
(SSR) will deliver static HTML that can be rendered to the screen before it&#39;s
interactive to user input—that is, before the JavaScript required to make the
content interactive has finished loading.&lt;/p&gt;
&lt;p&gt;For these types of applications, it can be very important to know whether the
first input occurred before or after
&lt;a href=&quot;https://en.wikipedia.org/wiki/Hydration_(web_development)&quot; rel=&quot;noopener&quot;&gt;hydration&lt;/a&gt;. If it
turns out that many people are attempting to interact with the page before
hydration completes, consider rendering your pages in a disabled or loading
state rather than in a state that looks interactive.&lt;/p&gt;
&lt;p&gt;If your application framework exposes the hydration timestamp, you can easily
compare that with the timestamp of the &lt;code&gt;first-input&lt;/code&gt; entry to determine whether
the first input happened before or after hydration. If your framework doesn&#39;t
expose that timestamp, or doesn&#39;t use hydration at all, another useful signal
may be whether input occurred before or after JavaScript finished loading.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;DOMContentLoaded&lt;/code&gt; event fires after the page&#39;s HTML has completely loaded
and parsed, which includes waiting for any synchronous, deferred, or module
scripts (including all statically imported modules) to load. So you can use the
timing of that event and compare it to when FID occurred.&lt;/p&gt;
&lt;p&gt;The following code observes &lt;code&gt;first-input&lt;/code&gt; entries and logs whether or not the
first input occurred prior to the end of the &lt;code&gt;DOMContentLoaded&lt;/code&gt; event:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fidEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; navEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntriesByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wasFIDBeforeDCL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;    fidEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startTime &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; navEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domContentLoadedEventStart&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;FID occurred before DOMContentLoaded:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; wasFIDBeforeDCL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;first-input&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; If your page uses &lt;code&gt;async&lt;/code&gt; scripts or dynamic &lt;code&gt;import()&lt;/code&gt; to load JavaScript, the &lt;code&gt;DOMContentLoaded&lt;/code&gt; event may not be a useful signal. Instead, you can consider using the &lt;code&gt;load&lt;/code&gt; event or—if there&#39;s a particular script you know takes a while to execute—you can use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Resource_Timing_API/Using_the_Resource_Timing_API&quot;&gt;Resource Timing&lt;/a&gt; entry for that script. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;identify-the-fid-target-element-and-event-type&quot;&gt;Identify the FID target element and event type &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#identify-the-fid-target-element-and-event-type&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Additional potentially-useful debug signals are the element that was interacted
with as well as the type of interaction it was (such as &lt;code&gt;mousedown&lt;/code&gt;, &lt;code&gt;keydown&lt;/code&gt;,
&lt;code&gt;pointerdown&lt;/code&gt;). While the interaction with the element itself does not
contribute to FID (remember FID is just the delay portion of the total event
latency), knowing which elements your users are interacting with may be useful
in determining how best to improve FID.&lt;/p&gt;
&lt;p&gt;For example, if the vast majority of your user&#39;s first interactions are with a
particular element, consider inlining the JavaScript code needed for that
element in the HTML, and lazy loading the rest.&lt;/p&gt;
&lt;p&gt;To get the interaction type and element associated with the first input event,
you can reference the &lt;code&gt;first-input&lt;/code&gt; entry&#39;s &lt;code&gt;target&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; properties:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fidEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;FID target element:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fidEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;FID interaction type:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fidEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;first-input&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;inp&quot;&gt;INP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#inp&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;INP is very similar to FID in that the most useful bits of information to
capture in the field are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What element was interacted with&lt;/li&gt;
&lt;li&gt;Why type of interaction it was&lt;/li&gt;
&lt;li&gt;When that interaction took place&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Like FID, a major cause of slow interactions is a blocked main thread, which can
be common while JavaScript is loading. Knowing whether most slow interactions
occur during page load is helpful in determining what needs to be done to fix
the problem.&lt;/p&gt;
&lt;p&gt;Unlike FID, the INP metric considers the full latency of an
interaction—including the time it takes to run any registered event listeners as
well as the time it takes to paint the next frame after all events listeners
have run. This means that for INP it&#39;s even more useful to know which target
elements tend to result in slow interactions, and what types of interactions
those are.&lt;/p&gt;
&lt;p&gt;Since INP and FID are both based on the Event Timing API, the way you determine
this information in JavaScript is very similar to the previous example. The
following code logs the target element and time (relative to &lt;code&gt;DOMContentLoaded&lt;/code&gt;)
of the INP entry.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;logINPDebugInfo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;inpEntry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;INP target element:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; inpEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;INP interaction type:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; inpEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; navEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntriesByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wasINPBeforeDCL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;    inpEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startTime &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; navEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;domContentLoadedEventStart&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;INP occurred before DCL:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; wasINPBeforeDCL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Note that this code doesn&#39;t show how to determine which &lt;code&gt;event&lt;/code&gt; entry is the INP
entry, as that logic is more involved. However, the following section explains
how to get this information using the
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;web-vitals&lt;/a&gt; JavaScript library.&lt;/p&gt;
&lt;h2 id=&quot;usage-with-the-web-vitals-javascript-library&quot;&gt;Usage with the web-vitals JavaScript library &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#usage-with-the-web-vitals-javascript-library&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The sections above offer some general suggestions and code examples to capture
debug info to include in the data you send to your analytics tool.&lt;/p&gt;
&lt;p&gt;Since version 3, the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/&quot; rel=&quot;noopener&quot;&gt;web-vitals&lt;/a&gt;
JavaScript library includes an &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#attribution-build&quot; rel=&quot;noopener&quot;&gt;attribution
build&lt;/a&gt; that
surfaces all of this information, and a few &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#attribution&quot; rel=&quot;noopener&quot;&gt;additional
signals&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;The following code example shows how you could set an additional &lt;a href=&quot;https://support.google.com/analytics/answer/11396839&quot; rel=&quot;noopener&quot;&gt;event
parameter&lt;/a&gt; (or &lt;a href=&quot;https://support.google.com/analytics/answer/2709828&quot; rel=&quot;noopener&quot;&gt;custom
dimension&lt;/a&gt;) containing a
debug string useful for helping to identify the root cause of performance
issues.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onCLS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onFID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onINP&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onLCP&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;web-vitals/attribution&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendToGoogleAnalytics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attribution&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; eventParams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;metric_value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;metric_id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CLS&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      eventParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug_string &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;largestShiftTarget&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LCP&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      eventParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug_string &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;FID&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;INP&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      eventParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug_string &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventTarget&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Assumes the global `gtag()` function exists, see:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// https://developers.google.com/analytics/devguides/collection/ga4&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; eventParams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onCLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToGoogleAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onLCP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToGoogleAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onFID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToGoogleAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onINP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToGoogleAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This code is specific to Google Analytics, but the general idea should easily
translate to other analytics tools as well.&lt;/p&gt;
&lt;p&gt;This code also just shows how to report on a single debug signal, but it may be
useful to be able to collect and report on multiple different signals per
metric. For example, to debug INP you might want to collect the interaction
type, time, and also the element being interacted with. The &lt;code&gt;web-vitals&lt;/code&gt;
attribution build exposes all of this information, as show in the following
example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onCLS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onFID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onINP&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onLCP&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;web-vitals/attribution&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendToGoogleAnalytics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attribution&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; eventParams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;metric_value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;metric_id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;INP&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      eventParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inp_target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventTarget&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      eventParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inp_type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventType&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      eventParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inp_time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      eventParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inp_load_state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; attribution&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loadState&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Additional metric logic...&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Assumes the global `gtag()` function exists, see:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// https://developers.google.com/analytics/devguides/collection/ga4&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; eventParams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onCLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToGoogleAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onLCP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToGoogleAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onFID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToGoogleAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onINP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToGoogleAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Refer to the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#attribution&quot; rel=&quot;noopener&quot;&gt;web-vitals attribution
documentation&lt;/a&gt; for the
complete list of debug signals exposed.&lt;/p&gt;
&lt;h2 id=&quot;report-and-visualize-the-data&quot;&gt;Report and visualize the data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#report-and-visualize-the-data&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once you&#39;ve started collecting debug information along with the metric values,
the next step is aggregating the data across all your users to start looking for
patterns and trends.&lt;/p&gt;
&lt;p&gt;As mentioned above, you don&#39;t necessarily need to address every single issue
your users are encountering, you want to address—especially at first—the issues
that are affecting the largest number of users, which should also be the issues
that have the largest negative impact on your Core Web Vitals scores.&lt;/p&gt;
&lt;h3 id=&quot;the-web-vitals-report-tool&quot;&gt;The Web Vitals Report tool &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#the-web-vitals-report-tool&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;re using the &lt;a href=&quot;https://github.com/GoogleChromeLabs/web-vitals-report&quot; rel=&quot;noopener&quot;&gt;Web Vitals
Report&lt;/a&gt; tool, it allows
you to additionally report on &lt;a href=&quot;https://github.com/GoogleChromeLabs/web-vitals-report#debug-dimension&quot; rel=&quot;noopener&quot;&gt;a single debug
dimension&lt;/a&gt;
for each of the Core Web Vitals metrics.&lt;/p&gt;
&lt;p&gt;Here&#39;s a screenshot from the Web Vitals Report debug info section, showing data
for the Web Vitals Report tool website itself:&lt;/p&gt;
&lt;img alt=&quot;Web Vitals Report showing debug information&quot; decoding=&quot;async&quot; height=&quot;535&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/Y49u3cmRD6RfAaZGCSmx.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Using the data above, you can see that whatever is causing the &lt;code&gt;section.Intro&lt;/code&gt;
element to shift is contributing the most to CLS on this page, so identifying
and fixing the cause of that shift will yield the greatest improvement to the
score.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/debug-performance-in-the-field/#summary&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hopefully this post has helped outline the specific ways you can use the
existing performance APIs and the &lt;code&gt;web-vitals&lt;/code&gt; library to get debug information
to help diagnose performance based on real users visits in the field. While this
guide is focused on the Core Web Vitals, the concepts also apply to debugging
any performance metric that&#39;s measurable in JavaScript.&lt;/p&gt;
&lt;p&gt;If you&#39;re just getting started measuring performance, and you&#39;re already a
Google Analytics user, the Web Vitals Report tool may be a good place to start
because it already supports reporting debug information for the Core Web
Vitals metrics.&lt;/p&gt;
&lt;p&gt;If you&#39;re an analytics vendor and you&#39;re looking to improve your products and
provide more debugging information to your users, consider some of the
techniques described here but don&#39;t limit yourself to &lt;em&gt;just&lt;/em&gt; the ideas presented
here. This post is intended to be generally applicable to all analytics tools;
however, individual analytics tools likely can (and should) capture and report
even more debug information.&lt;/p&gt;
&lt;p&gt;Lastly, if you feel there are gaps in your ability to debug these metrics due to
missing features or information in the APIs themselves send your feedback to
&lt;a href=&quot;mailto:web-vitals-feedback@googlegroups.com&quot; rel=&quot;noopener&quot;&gt;web-vitals-feedback@googlegroups.com&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>Back/forward cache</title>
    <link href="https://web.dev/bfcache/"/>
    <updated>2020-11-10T00:00:00Z</updated>
    <id>https://web.dev/bfcache/</id>
    <content type="html" mode="escaped">&lt;p&gt;Back/forward cache (or bfcache) is a browser optimization that enables instant
back and forward navigation. It significantly improves the browsing experience
for users—especially those with slower networks or devices.&lt;/p&gt;
&lt;p&gt;As web developers, it&#39;s critical to understand how to &lt;a href=&quot;https://web.dev/bfcache/#optimize-your-pages-for-bfcache&quot;&gt;optimize your pages for
bfcache&lt;/a&gt; across all browsers, so your users
can reap the benefits.&lt;/p&gt;
&lt;h2 id=&quot;browser-compatibility&quot;&gt;Browser compatibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#browser-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;bfcache has been supported in both
&lt;a href=&quot;https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/1.5/Using_Firefox_1.5_caching&quot; rel=&quot;noopener&quot;&gt;Firefox&lt;/a&gt;
and &lt;a href=&quot;https://webkit.org/blog/427/webkit-page-cache-i-the-basics/&quot; rel=&quot;noopener&quot;&gt;Safari&lt;/a&gt; for
many years, across desktop and mobile.&lt;/p&gt;
&lt;p&gt;Starting in version 86, Chrome enabled bfcache for
&lt;a href=&quot;https://web.dev/same-site-same-origin/&quot;&gt;cross-site&lt;/a&gt; navigations on Android for a small
percentage of users. In subsequent releases, additional support slowly rolled
out. Since version 96, bfcache is enabled for all Chrome users across desktop
and mobile.&lt;/p&gt;
&lt;h2 id=&quot;bfcache-basics&quot;&gt;bfcache basics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#bfcache-basics&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;bfcache is an in-memory cache that stores a complete snapshot of a page
(including the JavaScript heap) as the user is navigating away. With the entire
page in memory, the browser can quickly and easily restore it if the user
decides to return.&lt;/p&gt;
&lt;p&gt;How many times have you visited a website and clicked a link to go to another
page, only to realize it&#39;s not what you wanted and click the back button? In
that moment, bfcache can make a big difference in how fast the previous page
loads:&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
  &lt;table data-alignment=&quot;top&quot;&gt;
    &lt;tr&gt;
      &lt;td width=&quot;30%&quot;&gt;&lt;strong&gt;&lt;em&gt;Without&lt;/em&gt; bfcache enabled&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;
        A new request is initiated to load the previous page, and, depending
        on how well that page has been &lt;a href=&quot;https://web.dev/reliable/#the-options-in-your-caching-toolbox&quot;&gt;
        optimized&lt;/a&gt; for repeat visits, the browser might have to re-download,
        re-parse, and re-execute some (or all) of resources it just downloaded.
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;&lt;em&gt;With&lt;/em&gt; bfcache enabled&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;
        Loading the previous page is &lt;em&gt;essentially instant&lt;/em&gt;, because the
        entire page can be restored from memory, without having to go to the
        network at all
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Check out this video of bfcache in action to understand the speed up it can
bring to navigations:&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;cuPsdRckkF0&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;p&gt;In the video above, the example with bfcache is quite a bit faster than the
example without it.&lt;/p&gt;
&lt;p&gt;bfcache not only speeds up navigation, it also reduces data usage, since
resources do not have to be downloaded again.&lt;/p&gt;
&lt;p&gt;Chrome usage data shows that 1 in 10 navigations on desktop and 1 in 5 on mobile
are either back or forward. With bfcache enabled, browsers could eliminate the
data transfer and time spent loading for billions of web pages every single day!&lt;/p&gt;
&lt;h3 id=&quot;how-the-cache-works&quot;&gt;How the &amp;quot;cache&amp;quot; works &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#how-the-cache-works&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &amp;quot;cache&amp;quot; used by bfcache is different from the &lt;a href=&quot;https://web.dev/http-cache/&quot;&gt;HTTP cache&lt;/a&gt;
(which is also useful in speeding up repeat navigations). The bfcache is a
snapshot of the entire page in memory (including the JavaScript heap), whereas
the HTTP cache contains only the responses for previously made requests. Since
it&#39;s quite rare that all requests required to load a page can be fulfilled from
the HTTP cache, repeat visits using bfcache restores are always faster than even
the most well-optimized non-bfcache navigations.&lt;/p&gt;
&lt;p&gt;Creating a snapshot of a page in memory, however, involves some complexity in
terms of how best to preserve in-progress code. For example, how do you handle
&lt;code&gt;setTimeout()&lt;/code&gt; calls where the timeout is reached while the page is in the
bfcache?&lt;/p&gt;
&lt;p&gt;The answer is that browsers pause running any pending timers or unresolved
promises—essentially all pending tasks in the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#task-queue&quot; rel=&quot;noopener&quot;&gt;JavaScript task
queues&lt;/a&gt;—and
resume processing tasks when (or if) the page is restored from the bfcache.&lt;/p&gt;
&lt;p&gt;In some cases this is fairly low-risk (for example, timeouts or promises), but
in other cases it might lead to very confusing or unexpected behavior. For
example, if the browser pauses a task that&#39;s required as part of an &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IDBTransaction&quot; rel=&quot;noopener&quot;&gt;IndexedDB
transaction&lt;/a&gt;,
it can affect other open tabs in the same origin (since the same IndexedDB
databases can be accessed by multiple tabs simultaneously). As a result,
browsers will generally not attempt to cache pages in the middle of an IndexedDB
transaction or using APIs that might affect other pages.&lt;/p&gt;
&lt;p&gt;For more details on how various API usage affects a page&#39;s bfcache eligibility,
see &lt;a href=&quot;https://web.dev/bfcache/#optimize-your-pages-for-bfcache&quot;&gt;Optimize your pages for bfcache&lt;/a&gt; below.&lt;/p&gt;
&lt;h3 id=&quot;apis-to-observe-bfcache&quot;&gt;APIs to observe bfcache &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#apis-to-observe-bfcache&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While bfcache is an optimization that browsers do automatically, it&#39;s still
important for developers to know when it&#39;s happening so they can &lt;a href=&quot;https://web.dev/bfcache/#optimize-your-pages-for-bfcache&quot;&gt;optimize their
pages for it&lt;/a&gt; and &lt;a href=&quot;https://web.dev/bfcache/#implications-for-analytics-and-performance-measurement&quot;&gt;adjust any metrics or
performance
measurement&lt;/a&gt;
accordingly.&lt;/p&gt;
&lt;p&gt;The primary events used to observe bfcache are the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PageTransitionEvent&quot; rel=&quot;noopener&quot;&gt;page transition
events&lt;/a&gt;—&lt;code&gt;pageshow&lt;/code&gt;
and &lt;code&gt;pagehide&lt;/code&gt;—which have been around as long as bfcache has and are supported
in pretty much &lt;a href=&quot;https://caniuse.com/page-transition-events&quot; rel=&quot;noopener&quot;&gt;all browsers in use
today&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The newer &lt;a href=&quot;https://developer.chrome.com/blog/page-lifecycle-api/&quot; rel=&quot;noopener&quot;&gt;Page
Lifecycle&lt;/a&gt;
events—&lt;code&gt;freeze&lt;/code&gt; and &lt;code&gt;resume&lt;/code&gt;—are also dispatched when pages go in or out of the
bfcache, as well as in some other situations. For example when a background tab
gets frozen to minimize CPU usage. Note, the Page Lifecycle events are currently
only supported in Chromium-based browsers.&lt;/p&gt;
&lt;h4 id=&quot;observe-when-a-page-is-restored-from-bfcache&quot;&gt;Observe when a page is restored from bfcache &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#observe-when-a-page-is-restored-from-bfcache&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;pageshow&lt;/code&gt; event fires right after the &lt;code&gt;load&lt;/code&gt; event when the page is
initially loading and any time the page is restored from bfcache. The &lt;code&gt;pageshow&lt;/code&gt;
event has a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PageTransitionEvent/persisted&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;persisted&lt;/code&gt;&lt;/a&gt;
property which will be &lt;code&gt;true&lt;/code&gt; if the page was restored from bfcache
(and &lt;code&gt;false&lt;/code&gt; if not). You can use the &lt;code&gt;persisted&lt;/code&gt; property
to distinguish regular page loads from bfcache restores. For example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;persisted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;This page was restored from the bfcache.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;This page was loaded normally.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In browsers that support the Page Lifecycle API, the &lt;code&gt;resume&lt;/code&gt; event will also
fire when pages are restored from bfcache (immediately before the &lt;code&gt;pageshow&lt;/code&gt;
event), though it will also fire when a user revisits a frozen background tab.
If you want to restore a page&#39;s state after it&#39;s frozen (which includes pages in
the bfcache), you can use the &lt;code&gt;resume&lt;/code&gt; event, but if you want to measure your
site&#39;s bfcache hit rate, you&#39;d need to use the &lt;code&gt;pageshow&lt;/code&gt; event. In some cases,
you might need to use both.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; See &lt;a href=&quot;https://web.dev/bfcache/#how-bfcache-affects-analytics-and-performance-measurement&quot;&gt;Implications for performance and analytics&lt;/a&gt; for more details on bfcache measurement best practices. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;observe-when-a-page-is-entering-bfcache&quot;&gt;Observe when a page is entering bfcache &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#observe-when-a-page-is-entering-bfcache&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;pagehide&lt;/code&gt; event is the counterpart to the &lt;code&gt;pageshow&lt;/code&gt; event. The &lt;code&gt;pageshow&lt;/code&gt;
event fires when a page is either loaded normally or restored from the bfcache.
The &lt;code&gt;pagehide&lt;/code&gt; event fires when the page is either unloaded normally or when the
browser attempts to put it into the bfcache.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;pagehide&lt;/code&gt; event also has a &lt;code&gt;persisted&lt;/code&gt; property, and if it&#39;s &lt;code&gt;false&lt;/code&gt; then
you can be confident a page is not about to enter the bfcache. However, if the
&lt;code&gt;persisted&lt;/code&gt; property is &lt;code&gt;true&lt;/code&gt;, it doesn&#39;t guarantee that a page will be cached.
It means that the browser &lt;em&gt;intends&lt;/em&gt; to cache the page, but there may be factors
that make it impossible to cache.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pagehide&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;persisted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;This page *might* be entering the bfcache.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;This page will unload normally and be discarded.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Similarly, the &lt;code&gt;freeze&lt;/code&gt; event will fire immediately after the &lt;code&gt;pagehide&lt;/code&gt; event
(if the event&#39;s &lt;code&gt;persisted&lt;/code&gt; property is &lt;code&gt;true&lt;/code&gt;), but again that only means the
browser &lt;em&gt;intends&lt;/em&gt; to cache the page. It may still have to discard it for a
number of reasons explained below.&lt;/p&gt;
&lt;h2 id=&quot;optimize-your-pages-for-bfcache&quot;&gt;Optimize your pages for bfcache &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#optimize-your-pages-for-bfcache&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Not all pages get stored in bfcache, and even when a page does get stored there,
it won&#39;t stay there indefinitely. It&#39;s critical that developers understand what
makes pages eligible (and ineligible) for bfcache to maximize their cache-hit
rates.&lt;/p&gt;
&lt;p&gt;The following sections outline the best practices to make it as likely as
possible that the browser can cache your pages.&lt;/p&gt;
&lt;h3 id=&quot;never-use-the-unload-event&quot;&gt;Never use the &lt;code&gt;unload&lt;/code&gt; event &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#never-use-the-unload-event&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The most important way to optimize for bfcache in all browsers is to never use
the &lt;code&gt;unload&lt;/code&gt; event. Ever!&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;unload&lt;/code&gt; event is problematic for browsers because it predates bfcache and
many pages on the internet operate under the (reasonable) assumption that a page
will not continue to exist after the &lt;code&gt;unload&lt;/code&gt; event has fired. This presents a
challenge because many of those pages were &lt;em&gt;also&lt;/em&gt; built with the assumption that
the &lt;code&gt;unload&lt;/code&gt; event would fire any time a user is navigating away, which is no
longer true (and &lt;a href=&quot;https://developer.chrome.com/blog/page-lifecycle-api/#the-unload-event&quot; rel=&quot;noopener&quot;&gt;hasn&#39;t been true for a long
time&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;So browsers are faced with a dilemma, they have to choose between something that
can improve the user experience—but might also risk breaking the page.&lt;/p&gt;
&lt;p&gt;On desktop, Chrome and Firefox have chosen to make pages ineligible for bfcache if they add an &lt;code&gt;unload&lt;/code&gt;
listener, which is less risky but also disqualifies &lt;em&gt;a lot&lt;/em&gt; of pages. Safari
will attempt to cache some pages with an &lt;code&gt;unload&lt;/code&gt; event listener, but to reduce
potential breakage it will not run the &lt;code&gt;unload&lt;/code&gt; event when a user is navigating
away, which makes the event very unreliable.&lt;/p&gt;
&lt;p&gt;On mobile, Chrome and Safari will attempt to cache pages with an &lt;code&gt;unload&lt;/code&gt; event listener since the risk of breakage is lower due to the fact that the &lt;code&gt;unload&lt;/code&gt; event has always been extremely unreliable on mobile. Firefox treats pages that use &lt;code&gt;unload&lt;/code&gt; as ineligible for the bfcache, except on iOS, which requires all browsers to use the WebKit rendering engine, and so it behaves like Safari.&lt;/p&gt;
&lt;p&gt;Instead of using the &lt;code&gt;unload&lt;/code&gt; event, use the &lt;code&gt;pagehide&lt;/code&gt; event. The &lt;code&gt;pagehide&lt;/code&gt;
event fires in all cases where the &lt;code&gt;unload&lt;/code&gt; event currently fires, and it
&lt;em&gt;also&lt;/em&gt; fires when a page is put in the bfcache.&lt;/p&gt;
&lt;p&gt;In fact, &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt; has a &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse/pull/11085&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;no-unload-listeners&lt;/code&gt; audit&lt;/a&gt;, which will warn developers if any JavaScript on their pages (including that from third-party libraries) adds an &lt;code&gt;unload&lt;/code&gt; event listener.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Never add an &lt;code&gt;unload&lt;/code&gt; event listener! Use the &lt;code&gt;pagehide&lt;/code&gt; event instead. Adding an &lt;code&gt;unload&lt;/code&gt; event listener will make your site slower in Firefox, and the code won&#39;t even run most of the time in Chrome and Safari. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;only-add-beforeunload-listeners-conditionally&quot;&gt;Only add &lt;code&gt;beforeunload&lt;/code&gt; listeners conditionally &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#only-add-beforeunload-listeners-conditionally&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;beforeunload&lt;/code&gt; event will not make your pages ineligible for bfcache in
Chrome or Safari, but it will make them ineligible in Firefox, so avoid using it
unless absolutely necessary.&lt;/p&gt;
&lt;p&gt;Unlike the &lt;code&gt;unload&lt;/code&gt; event, however, there are legitimate uses for
&lt;code&gt;beforeunload&lt;/code&gt;. For example, when you want to warn the user that they have
unsaved changes they&#39;ll lose if they leave the page. In this case, it&#39;s
recommended that you only add &lt;code&gt;beforeunload&lt;/code&gt; listeners when a user has unsaved
changes and then remove them immediately after the unsaved changes are saved.&lt;/p&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;worse&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Don&#39;t&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;beforeunload&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pageHasUnsavedChanges&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;returnValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Are you sure you want to exit?&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;figcaption class=&quot;compare__caption&quot;&gt;
&lt;p&gt;The code above adds a &lt;code&gt;beforeunload&lt;/code&gt; listener unconditionally.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;better&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Do&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;beforeUnloadListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;returnValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Are you sure you want to exit?&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// A function that invokes a callback when the page has unsaved changes.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onPageHasUnsavedChanges&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;beforeunload&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; beforeUnloadListener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// A function that invokes a callback when the page&#39;s unsaved changes are resolved.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onAllChangesSaved&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;beforeunload&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; beforeUnloadListener&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;figcaption class=&quot;compare__caption&quot;&gt;
&lt;p&gt;The code above only adds the &lt;code&gt;beforeunload&lt;/code&gt; listener when it&#39;s needed (and
removes it when it&#39;s not).&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;minimize-use-of-cache-control-no-store&quot;&gt;Minimize use of &lt;code&gt;Cache-Control: no-store&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#minimize-use-of-cache-control-no-store&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Cache-Control: no-store&lt;/code&gt; is an HTTP header web servers can set on responses that instructs the browser not to store the response in any HTTP cache. This should be used for resources containing sensitive user information, for example pages behind a login.&lt;/p&gt;
&lt;p&gt;Though bfcache is not an HTTP cache, historically, when &lt;code&gt;Cache-Control: no-store&lt;/code&gt; is set on the page resource itself (as opposed to any subresource), browsers have chosen not to store the page in bfcache. There is &lt;a href=&quot;https://github.com/fergald/explainer-bfcache-ccns/blob/main/README.md&quot; rel=&quot;noopener&quot;&gt;work currently underway to change this behavior for Chrome&lt;/a&gt; in a privacy-preserving manner, but at present any pages using &lt;code&gt;Cache-Control: no-store&lt;/code&gt; will not be eligible for bfcache.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;Cache-Control: no-store&lt;/code&gt; restricts a page&#39;s eligibility for bfcache, it should only be set on pages that contain sensitive information where caching of any sort is never appropriate.&lt;/p&gt;
&lt;p&gt;For pages that wish to always serve up-to-date content—and that content does not contain sensitive information—use &lt;code&gt;Cache-Control: no-cache&lt;/code&gt; or &lt;code&gt;Cache-Control: max-age=0&lt;/code&gt;. These directives instruct the browser to revalidate the content before serving it, and they do not affect a page&#39;s bfcache eligibility.&lt;/p&gt;
&lt;p&gt;Note that when a page is restored from bfcache, it is restored from memory, not from the HTTP cache. As a result, directives like &lt;code&gt;Cache-Control: no-cache&lt;/code&gt; or &lt;code&gt;Cache-Control: max-age=0&lt;/code&gt; are not taken into account, and no revalidation occurs before the content is displayed to the user.&lt;/p&gt;
&lt;p&gt;This is still likely a better user experience, however, as bfcache restores are instant and—since pages do not stay in the bfcache for very long—it&#39;s unlikely that the content is out of date. However, if your content does change minute-by-minute, you can fetch any updates using the &lt;code&gt;pageshow&lt;/code&gt; event, as outlined in the next section.&lt;/p&gt;
&lt;h3 id=&quot;update-stale-or-sensitive-data-after-bfcache-restore&quot;&gt;Update stale or sensitive data after bfcache restore &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#update-stale-or-sensitive-data-after-bfcache-restore&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If your site keeps user state—especially any sensitive user information—that
data needs to be updated or cleared after a page is restored from bfcache.&lt;/p&gt;
&lt;p&gt;For example, if a user navigates to a checkout page and then updates their
shopping cart, a back navigation could potentially expose out-of-date
information if a stale page is restored from bfcache.&lt;/p&gt;
&lt;p&gt;Another, more critical example is if a user signs out of a site on a public
computer and the next user clicks the back button. This could potentially expose
private data that the user assumed was cleared when they logged out.&lt;/p&gt;
&lt;p&gt;To avoid situations like this, it&#39;s good to always update the page after a
&lt;code&gt;pageshow&lt;/code&gt; event if &lt;code&gt;event.persisted&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The following code checks for the presence of a site-specific cookie in the
&lt;code&gt;pageshow&lt;/code&gt; event and reloads if the cookie is not found:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;persisted &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookie&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;my-cookie&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Force a reload if the user has logged out.&lt;/span&gt;&lt;br /&gt;    location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;avoid-windowopener-references&quot;&gt;Avoid &lt;code&gt;window.opener&lt;/code&gt; references &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#avoid-windowopener-references&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In some browsers (including Chromium-based browsers) if a page was opened using
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/open&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;window.open()&lt;/code&gt;&lt;/a&gt;
or (in &lt;a href=&quot;https://crbug.com/898942&quot; rel=&quot;noopener&quot;&gt;Chromium-based browsers prior to version 88&lt;/a&gt;) from a link with
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/a#target&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;target=_blank&lt;/code&gt;&lt;/a&gt;—without
specifying
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Link_types/noopener&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;rel=&amp;quot;noopener&amp;quot;&lt;/code&gt;&lt;/a&gt;—then
the opening page will have a reference to the window object of the opened page.&lt;/p&gt;
&lt;p&gt;In addition to &lt;a href=&quot;https://mathiasbynens.github.io/rel-noopener/&quot; rel=&quot;noopener&quot;&gt;being a security
risk&lt;/a&gt;, a page with a non-null
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/opener&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;window.opener&lt;/code&gt;&lt;/a&gt;
reference cannot safely be put into the bfcache because that could break any
pages attempting to access it.&lt;/p&gt;
&lt;p&gt;As a result, it&#39;s best to avoid creating &lt;code&gt;window.opener&lt;/code&gt; references. You can do this by using
&lt;code&gt;rel=&amp;quot;noopener&amp;quot;&lt;/code&gt; whenever possible. If your site requires opening a window and
controlling it through
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/postMessage&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;window.postMessage()&lt;/code&gt;&lt;/a&gt;
or directly referencing the window object, neither the opened window nor the
opener will be eligible for the bfcache.&lt;/p&gt;
&lt;h3 id=&quot;always-close-open-connections-before-the-user-navigates-away&quot;&gt;Always close open connections before the user navigates away &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#always-close-open-connections-before-the-user-navigates-away&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As mentioned above, when a page is put into the bfcache all scheduled JavaScript
tasks are paused and then resumed when the page is taken out of the cache.&lt;/p&gt;
&lt;p&gt;If these scheduled JavaScript tasks are only accessing DOM APIs—or other APIs
isolated to just the current page—then pausing these tasks while the page is not
visible to the user is not going to cause any problems.&lt;/p&gt;
&lt;p&gt;However, if these tasks are connected to APIs that are also accessible from
other pages in the same origin (for example: IndexedDB, Web Locks, WebSockets,
etc.) this can be problematic because pausing these tasks may prevent code in
other tabs from running.&lt;/p&gt;
&lt;p&gt;As a result, some browsers will not attempt to put a page in bfcache in the
following scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pages with an open &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IDBOpenDBRequest&quot; rel=&quot;noopener&quot;&gt;IndexedDB
connection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pages with in-progress
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Fetch_API&quot; rel=&quot;noopener&quot;&gt;fetch()&lt;/a&gt; or
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/XMLHttpRequest&quot; rel=&quot;noopener&quot;&gt;XMLHttpRequest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pages with an open
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebSocket&quot; rel=&quot;noopener&quot;&gt;WebSocket&lt;/a&gt; or
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebRTC_API&quot; rel=&quot;noopener&quot;&gt;WebRTC&lt;/a&gt;
connection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your page is using any of these APIs, it&#39;s best to always close connections
and remove or disconnect observers during the &lt;code&gt;pagehide&lt;/code&gt; or &lt;code&gt;freeze&lt;/code&gt; event. That
will allow the browser to safely cache the page without the risk of it affecting
other open tabs.&lt;/p&gt;
&lt;p&gt;Then, if the page is restored from the bfcache, you can re-open or re-connect to
those APIs (in the &lt;code&gt;pageshow&lt;/code&gt; or &lt;code&gt;resume&lt;/code&gt; event).&lt;/p&gt;
&lt;p&gt;The following example shows how to ensure your pages are eligible for bfcache
when using IndexedDB by closing an open connection in the &lt;code&gt;pagehide&lt;/code&gt; event
listener:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dbPromise&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;openDB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;dbPromise&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    dbPromise &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; req &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; indexedDB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-db&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onupgradeneeded&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;keyval&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onsuccess&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; dbPromise&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Close the connection to the database when the user is leaving.&lt;/span&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pagehide&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dbPromise&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    dbPromise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    dbPromise &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Open the connection when the page is loaded or restored from bfcache.&lt;/span&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;openDB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;test-to-ensure-your-pages-are-cacheable&quot;&gt;Test to ensure your pages are cacheable &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#test-to-ensure-your-pages-are-cacheable&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Chrome DevTools can help you test your pages to ensure they&#39;re optimized for
bfcache, and identify any issues that may be preventing them from being
eligible.&lt;/p&gt;
&lt;p&gt;To test a particular page, navigate to it in Chrome and then in DevTools go to
&lt;strong&gt;Application&lt;/strong&gt; &amp;gt; &lt;strong&gt;Back-forward Cache&lt;/strong&gt;. Next click the &lt;strong&gt;Run Test&lt;/strong&gt; button and
DevTools will attempt to navigate away and back to determine whether the page
could be restored from bfcache.&lt;/p&gt;
&lt;img alt=&quot;Back-forward cache panel in DevTools&quot; decoding=&quot;async&quot; height=&quot;313&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/QafTzULUNflaSh77zBgT.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The Back/forward Cache feature in DevTools is currently in active development. We strongly encourage developers to test their pages in Chrome Canary to ensure they&#39;re running the latest version of DevTools and getting the most up-to-date bfcache recommendations. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;If successful, the panel will report &amp;quot;Restored from back-forward cache&amp;quot;:&lt;/p&gt;
&lt;img alt=&quot;DevTools reporting a page was successfully restored from bfcache&quot; decoding=&quot;async&quot; height=&quot;313&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vPwN0z95ZBTiwZIpdZT4.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;If unsuccessful, the panel will indicate the page was not restored and list the
reason why. If the reason is something you as a developer can address, that
will also be indicated:&lt;/p&gt;
&lt;img alt=&quot;DevTools reporting failure to restore a page from bfcache&quot; decoding=&quot;async&quot; height=&quot;313&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/ji3ew4DoP6joKdJvtGwa.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;In the screenshot above, the use of an &lt;code&gt;unload&lt;/code&gt; event listener is
&lt;a href=&quot;https://web.dev/bfcache/#never-use-the-unload-event&quot;&gt;preventing&lt;/a&gt; the page from being eligible
for bfcache. You can fix that by switching from &lt;code&gt;unload&lt;/code&gt; to using &lt;code&gt;pagehide&lt;/code&gt; instead:&lt;/p&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;worse&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Don&#39;t&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;unload&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;better&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Do&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pagehide&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;Lighthouse 10.0 also &lt;a href=&quot;https://developer.chrome.com/blog/lighthouse-10-0/#bfcache&quot; rel=&quot;noopener&quot;&gt;added a bfcache audit&lt;/a&gt;, which performs a similar test to the one DevTools does, and also provides reasons why the page is ineligible if the audit fails. Take a look at the &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/bf-cache/&quot; rel=&quot;noopener&quot;&gt;bfcache audit&#39;s docs&lt;/a&gt; for more information.&lt;/p&gt;
&lt;h2 id=&quot;how-bfcache-affects-analytics-and-performance-measurement&quot;&gt;How bfcache affects analytics and performance measurement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#how-bfcache-affects-analytics-and-performance-measurement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you track visits to your site with an analytics tool, you will likely notice
a decrease in the total number of pageviews reported as Chrome continues to
enable bfcache for more users.&lt;/p&gt;
&lt;p&gt;In fact, you&#39;re likely &lt;em&gt;already&lt;/em&gt; underreporting pageviews from other browsers
that implement bfcache since most of the popular analytics libraries do not
track bfcache restores as new pageviews.&lt;/p&gt;
&lt;p&gt;If you don&#39;t want your pageview counts to go down due to Chrome enabling
bfcache, you can report bfcache restores as pageviews (recommended) by listening
to the &lt;code&gt;pageshow&lt;/code&gt; event and checking the &lt;code&gt;persisted&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;The following example shows how to do this with Google Analytics; the logic
should be similar for other analytics tools:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Send a pageview when the page is first loaded.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;page_view&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Send another pageview if the page is restored from bfcache.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;persisted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;page_view&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;measuring-your-bfcache-hit-ratio&quot;&gt;Measuring your bfcache hit ratio &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#measuring-your-bfcache-hit-ratio&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You may also wish to track whether the bfcache was used, to help identify pages
that are not utilizing the bfcache. For example, with an event:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pageshow&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// You can measure bfcache hit rate by tracking all bfcache restores and&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// other back/forward navigations via a separate event.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; navigationType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntriesByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;persisted &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; navigationType &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;back_forward&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;gtag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;back_forward_navigation&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token string-property property&quot;&gt;&#39;isBFCache&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;persisted&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;It is important to realize that there are a number of scenarios, outside
of the site owners control, when a Back/Forward navigation will not use
the bfcache, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;when the user quits the browser and starts it again&lt;/li&gt;
&lt;li&gt;when the user duplicates a tab&lt;/li&gt;
&lt;li&gt;when the user closes a tab and uncloses it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even without those exclusions the bfcache will be discarded after a period to conserve memory.&lt;/p&gt;
&lt;p&gt;So, website owners should not be expecting a 100% bfcache hit ratio for all
&lt;code&gt;back_forward&lt;/code&gt; navigations. However, measuring their ratio can be useful to
identify pages where the page itself is preventing bfcache usage for a high
proportion of back and forward navigations.&lt;/p&gt;
&lt;p&gt;The Chrome team is working on a
&lt;a href=&quot;https://github.com/rubberyuzu/bfcache-not-retored-reason/blob/main/NotRestoredReason.md&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;NotRestoredReasons&lt;/code&gt; API&lt;/a&gt;
to help expose the reasons why the bfcache was not used to help developers
understand the reasoning the cache was not used and if this is something they
can work on to improve for their sites.&lt;/p&gt;
&lt;h3 id=&quot;performance-measurement&quot;&gt;Performance measurement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#performance-measurement&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;bfcache can also negatively affect performance metrics collected &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;in the
field&lt;/a&gt;, specifically metrics
that measure page load times.&lt;/p&gt;
&lt;p&gt;Since bfcache navigations restore an existing page rather than initiate a new
page load, the total number of page loads collected will decrease when bfcache
is enabled. What&#39;s critical, though, is that the page loads being replaced by
bfcache restores would likely have been some of the fastest page loads in your
dataset. This is because back and forward navigations, by definition, are repeat
visits, and repeat page loads are generally faster than page loads from first
time visitors (due to &lt;a href=&quot;https://web.dev/http-cache/&quot;&gt;HTTP caching&lt;/a&gt;, as mentioned earlier).&lt;/p&gt;
&lt;p&gt;The result is fewer fast page loads in your dataset, which will likely skew the
distribution slower—despite the fact that the performance experienced by the
user has probably improved!&lt;/p&gt;
&lt;p&gt;There are a few ways to deal with this issue. One is to annotate all page load
metrics with their respective &lt;a href=&quot;https://www.w3.org/TR/navigation-timing-2/#sec-performance-navigation-types&quot; rel=&quot;noopener&quot;&gt;navigation
type&lt;/a&gt;:
&lt;code&gt;navigate&lt;/code&gt;, &lt;code&gt;reload&lt;/code&gt;, &lt;code&gt;back_forward&lt;/code&gt;, or &lt;code&gt;prerender&lt;/code&gt;. This will allow you to
continue to monitor your performance within these navigation types—even if the
overall distribution skews negative. This approach is recommended for
non-user-centric page load metrics like &lt;a href=&quot;https://web.dev/ttfb/&quot;&gt;Time to First Byte
(TTFB)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For user-centric metrics like the &lt;a href=&quot;https://web.dev/vitals/&quot;&gt;Core Web Vitals&lt;/a&gt;, a better option
is to report a value that more accurately represents what the user experiences.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The &lt;code&gt;back_forward&lt;/code&gt; navigation type in the &lt;a href=&quot;https://www.w3.org/TR/navigation-timing-2/#sec-performance-navigation-types&quot;&gt;Navigation Timing API&lt;/a&gt; is not to be confused with bfcache restores. The Navigation Timing API only annotates page loads, whereas bfcache restores are re-using a page loaded from a previous navigation. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;impact-on-core-web-vitals&quot;&gt;Impact on Core Web Vitals &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#impact-on-core-web-vitals&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/vitals/&quot;&gt;Core Web Vitals&lt;/a&gt; measure the user&#39;s experience of a web page across a
variety of dimensions (loading speed, interactivity, visual stability), and
since users experience bfcache restores as faster navigations than traditional
page loads, it&#39;s important that the Core Web Vitals metrics reflect this. After
all, a user doesn&#39;t care whether or not bfcache was enabled, they just care that
the navigation was fast!&lt;/p&gt;
&lt;p&gt;Tools like the &lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience
Report&lt;/a&gt;,
that collect and report on the Core Web Vitals metrics treat bfcache restores as
separate page visits in their dataset.&lt;/p&gt;
&lt;p&gt;And while there aren&#39;t (yet) dedicated web performance APIs for measuring these
metrics after bfcache restores, their values can be approximated using existing
web APIs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For &lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;, you can use the delta between
the &lt;code&gt;pageshow&lt;/code&gt; event&#39;s timestamp and the timestamp of the next painted frame
(since all elements in the frame will be painted at the same time). Note
that in the case of a bfcache restore, LCP and FCP will be the same.&lt;/li&gt;
&lt;li&gt;For &lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt;, you can re-add the event listeners
(the same ones used by the &lt;a href=&quot;https://github.com/GoogleChromeLabs/first-input-delay&quot; rel=&quot;noopener&quot;&gt;FID
polyfill&lt;/a&gt;) in the
&lt;code&gt;pageshow&lt;/code&gt; event, and report FID as the delay of the first input after the
bfcache restore.&lt;/li&gt;
&lt;li&gt;For &lt;a href=&quot;https://web.dev/cls/&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt;, you can continue to keep using
your existing Performance Observer; all you have to do is reset the current
CLS value to 0.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For more details on how bfcache affects each metric, refer to the individual
Core Web Vitals &lt;a href=&quot;https://web.dev/vitals/#core-web-vitals&quot;&gt;metric guides pages&lt;/a&gt;. And for a
specific example of how to implement bfcache versions of these metrics in code,
refer to the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/pull/87&quot; rel=&quot;noopener&quot;&gt;PR adding them to the web-vitals JS
library&lt;/a&gt;.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; As of &lt;code&gt;v1&lt;/code&gt;, the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;web-vitals&lt;/a&gt; JavaScript library &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/pull/87&quot;&gt;supports bfcache restores&lt;/a&gt; in the metrics it reports. Developers using &lt;code&gt;v1&lt;/code&gt; or greater should not need to update their code. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;additional-resources&quot;&gt;Additional Resources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/bfcache/#additional-resources&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/Firefox/Releases/1.5/Using_Firefox_1.5_caching&quot; rel=&quot;noopener&quot;&gt;Firefox
Caching&lt;/a&gt;
&lt;em&gt;(bfcache in Firefox)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webkit.org/blog/427/webkit-page-cache-i-the-basics/&quot; rel=&quot;noopener&quot;&gt;Page Cache&lt;/a&gt;
&lt;em&gt;(bfcache in Safari)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.google.com/document/d/1JtDCN9A_1UBlDuwkjn1HWxdhQ1H2un9K4kyPLgBqJUc/edit?usp=sharing&quot; rel=&quot;noopener&quot;&gt;Back/forward cache: web exposed
behavior&lt;/a&gt;
&lt;em&gt;(bfcache differences across browsers)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://back-forward-cache-tester.glitch.me/?persistent_logs=1&quot; rel=&quot;noopener&quot;&gt;bfcache
tester&lt;/a&gt;
&lt;em&gt;(test how different APIs and events affect bfcache in browsers)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2022/05/performance-game-changer-back-forward-cache/&quot; rel=&quot;noopener&quot;&gt;Performance Game Changer: Browser Back/Forward Cache&lt;/a&gt;
&lt;em&gt;(a case study from Smashing Magazine showing dramatic Core Web Vitals improvements by enabling bfcache)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author><author>
      <name>Barry Pollard</name>
    </author>
  </entry>
  
  <entry>
    <title>Best practices for measuring Web Vitals in the field</title>
    <link href="https://web.dev/vitals-field-measurement-best-practices/"/>
    <updated>2020-05-27T00:00:00Z</updated>
    <id>https://web.dev/vitals-field-measurement-best-practices/</id>
    <content type="html" mode="escaped">&lt;p&gt;Having the ability to measure and report on the real-world performance of your
pages is critical for diagnosing and improving performance over time. Without
&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;field
data&lt;/a&gt;,
it&#39;s impossible to know for sure whether the changes you&#39;re making to your site
are actually achieving their desired results.&lt;/p&gt;
&lt;p&gt;Many popular &lt;a href=&quot;https://en.wikipedia.org/wiki/Real_user_monitoring&quot; rel=&quot;noopener&quot;&gt;Real User Monitoring
(RUM)&lt;/a&gt; analytics providers
already support the &lt;a href=&quot;https://web.dev/vitals/#core-web-vitals&quot;&gt;Core Web Vitals&lt;/a&gt; metrics in their
tools (as well as many &lt;a href=&quot;https://web.dev/vitals/#other-web-vitals&quot;&gt;other Web Vitals&lt;/a&gt;). If you&#39;re
currently using one of these RUM analytics tools, you&#39;re in great shape to
assess how well the pages on your site meet the &lt;a href=&quot;https://web.dev/vitals/#core-web-vitals&quot;&gt;recommended Core Web Vitals
thresholds&lt;/a&gt; and prevent regressions
in the future.&lt;/p&gt;
&lt;p&gt;While we do recommend using an analytics tool that supports the Core Web Vitals
metrics, if the analytics tool you&#39;re currently using does not support them, you
don&#39;t necessarily need to switch. Almost all analytics tools offer a way to
define and measure &lt;a href=&quot;https://support.google.com/analytics/answer/2709828&quot; rel=&quot;noopener&quot;&gt;custom
metrics&lt;/a&gt; or
&lt;a href=&quot;https://support.google.com/analytics/answer/1033068&quot; rel=&quot;noopener&quot;&gt;events&lt;/a&gt;, which means you
can likely use your current analytics provider to measure the Core Web Vitals
metrics and add them to your existing analytics reports and dashboards.&lt;/p&gt;
&lt;p&gt;This guide discusses best practices for measuring Core Web Vitals metrics (or any custom metrics) with a third-party or in-house analytics tool. It can also serve as a guide for analytics vendors wishing to add Core Web Vitals support to their service.&lt;/p&gt;
&lt;h2 id=&quot;use-custom-metrics-or-events&quot;&gt;Use custom metrics or events &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#use-custom-metrics-or-events&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As mentioned above, most analytics tools let you measure custom data. If your
analytics tool supports this, you should be able to measure each of the Core Web
Vitals metrics using this mechanism.&lt;/p&gt;
&lt;p&gt;Measuring custom metrics or events in an analytics tool is generally a
three-step process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/analytics/answer/2709829?hl=en&amp;amp;ref_topic=2709827&quot; rel=&quot;noopener&quot;&gt;Define or
register&lt;/a&gt;
the custom metric in your tool&#39;s admin (if required). &lt;em&gt;(Note: not all
analytics providers require custom metrics to be defined ahead of time.)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Compute the value of the metric in your frontend JavaScript code.&lt;/li&gt;
&lt;li&gt;Send the metric value to your analytics backend, ensuring the name or ID
matches what was defined in step 1 &lt;em&gt;(again, if required)&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For steps 1 and 3, you can refer to your analytics tool&#39;s documentation for
instructions. For step 2 you can use the
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;web-vitals&lt;/a&gt; JavaScript library to
compute the value of each of the Core Web Vitals metrics.&lt;/p&gt;
&lt;p&gt;The following code sample shows how easy it can be to track these metrics in
code and send them to an analytics service.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onCLS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onFID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onLCP&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;web-vitals&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendToAnalytics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sendBeacon &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendBeacon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/analytics&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/analytics&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;POST&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;keepalive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onCLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onFID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onLCP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;avoid-averages&quot;&gt;Avoid averages &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#avoid-averages&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It&#39;s tempting to sum up a range of values for a performance metric by
calculating an average. Averages seem convenient at first glance, as they&#39;re a
tidy summary of a large quantity of data, but you should resist the urge to rely
on them to interpret page performance.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.igvita.com/2016/01/12/the-average-page-is-a-myth/&quot; rel=&quot;noopener&quot;&gt;Averages are problematic&lt;/a&gt;
because they don&#39;t represent any single user&#39;s session. Outliers at either range
of the distribution may skew the average in ways that are misleading.&lt;/p&gt;
&lt;p&gt;For example, a small group of users may be on extremely slow networks or devices
that are toward the maximum range of values, but don&#39;t account for enough user
sessions to impact the average in a way that suggests there&#39;s a problem.&lt;/p&gt;
&lt;p&gt;Whenever possible, rely on percentiles instead of averages. Percentiles across a
distribution for a given performance metric better describe the full range of
user experiences for your website. This allows you to focus on subsets of
&lt;em&gt;actual&lt;/em&gt; experiences, which will give you more insight than a single value ever
could.&lt;/p&gt;
&lt;h2 id=&quot;ensure-you-can-report-a-distribution&quot;&gt;Ensure you can report a distribution &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#ensure-you-can-report-a-distribution&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once you&#39;ve computed the values for each of the Core Web Vitals metrics and sent
them to your analytics service using a custom metric or event, the next step is
to build a report or dashboard displaying the values that have been collected.&lt;/p&gt;
&lt;p&gt;To ensure you&#39;re meeting the &lt;a href=&quot;https://web.dev/vitals/#core-web-vitals&quot;&gt;recommended Core Web Vitals
thresholds&lt;/a&gt;, you&#39;ll need your report
to display the value of each metric at the 75th percentile.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; While the 75th percentile is a great place to start, it may be worth observing the 90th or even the 95th percentile. This will help you to understand the user experience for those who are on very slow networks or devices. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;If your analytics tool does not offer quantile reporting as a built-in feature,
you can probably still get this data manually by generating a report that lists
every metric value sorted in ascending order. Once this report is generated, the
result that is 75% of the way through the full, sorted list of all values in
that report will be the 75th percentile for that metric—and this will be the
case no matter how you segment your data (by device type, connection type,
country, etc.).&lt;/p&gt;
&lt;p&gt;If your analytic tool does not give you metric-level reporting granularity by
default, you can probably achieve the same result if your analytics tool
supports &lt;a href=&quot;https://support.google.com/analytics/answer/2709828&quot; rel=&quot;noopener&quot;&gt;custom
dimensions&lt;/a&gt;. By setting a
unique, custom dimension value for each individual metric instance you track,
you should be able to generate a report, broken down by individual metric
instances, if you include the custom dimension in the report configuration.
Since each instance will have a unique dimension value, no grouping will occur.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/GoogleChromeLabs/web-vitals-report&quot; rel=&quot;noopener&quot;&gt;Web Vitals Report&lt;/a&gt;
is an example of this technique that uses Google Analytics. The code for the
report is &lt;a href=&quot;https://github.com/GoogleChromeLabs/web-vitals-report&quot; rel=&quot;noopener&quot;&gt;open source&lt;/a&gt;,
so developers can reference it as an example of the techniques outlined in this
section.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/326742/101584324-3f9a0900-3992-11eb-8f2d-182f302fb67b.png&quot; alt=&quot;Screenshots of the Web Vitals
Report&quot; /&gt;&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Tip: The &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;&lt;code&gt;web-vitals&lt;/code&gt;&lt;/a&gt; JavaScript library provides an ID for each metric instance reported making it possible to build distributions in most analytics tools. See the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#metric&quot;&gt;&lt;code&gt;Metric&lt;/code&gt;&lt;/a&gt; interface documentation for more details. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;send-your-data-at-the-right-time&quot;&gt;Send your data at the right time &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#send-your-data-at-the-right-time&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some performance metrics can be calculated once the page has finished loading,
while others (like CLS) consider the entire lifespan of the page—and are only
final once the page has started unloading.&lt;/p&gt;
&lt;p&gt;This can be problematic, however, since both the &lt;code&gt;beforeunload&lt;/code&gt; and &lt;code&gt;unload&lt;/code&gt;
events are not reliable (especially on mobile) and their use is &lt;a href=&quot;https://developer.chrome.com/blog/page-lifecycle-api/#legacy-lifecycle-apis-to-avoid&quot; rel=&quot;noopener&quot;&gt;not
recommended&lt;/a&gt;
(since they can prevent a page from being eligible for the &lt;a href=&quot;https://developer.chrome.com/blog/page-lifecycle-api/#what-is-the-back-forward-cache&quot; rel=&quot;noopener&quot;&gt;Back-Forward
Cache&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;For metrics that track the entire lifespan of a page, it&#39;s best to send whatever
the metric&#39;s current value is during the &lt;code&gt;visibilitychange&lt;/code&gt; event, whenever the
page&#39;s visibility state changes to &lt;code&gt;hidden&lt;/code&gt;. This is because—once the page&#39;s
visibility state changes to &lt;code&gt;hidden&lt;/code&gt;—there&#39;s no guarantee that any script on
that page will be able to run again. This is especially true on mobile operating
systems where the browser app itself can be closed without any page callbacks
being fired.&lt;/p&gt;
&lt;p&gt;Note that mobile operating systems do generally fire the &lt;code&gt;visibilitychange&lt;/code&gt;
event when switching tabs, switching apps, or closing the browser app itself.
They also fire the &lt;code&gt;visibilitychange&lt;/code&gt; event when closing a tab or navigating to
a new page. This makes the &lt;code&gt;visibilitychange&lt;/code&gt; event far more reliable than the
&lt;code&gt;unload&lt;/code&gt; or &lt;code&gt;beforeunload&lt;/code&gt; events.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Due to &lt;a href=&quot;https://github.com/w3c/page-visibility/issues/59#issue-554880545&quot;&gt;some browser bugs&lt;/a&gt;, there are a few cases where the &lt;code&gt;visibilitychange&lt;/code&gt; event does not fire. If you&#39;re building your own analytics library, it&#39;s important to be aware of these bugs. Note that the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;web-vitals&lt;/a&gt; JavaScript library does account for all of these bugs. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;monitor-performance-over-time&quot;&gt;Monitor performance over time &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#monitor-performance-over-time&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once you&#39;ve updated your analytics implementation to both track and report on
the Core Web Vitals metrics, the next step is to track how changes to your site
affect performance over time.&lt;/p&gt;
&lt;h3 id=&quot;version-your-changes&quot;&gt;Version your changes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#version-your-changes&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A naive (and ultimately unreliable) approach to tracking changes is to deploy
changes to production and then assume that all metrics received after the
deployment date correspond to the new site and all metrics received before the
deployment date correspond to the old site. However, any number of factors
(including caching at the HTTP, service worker, or CDN layer) can prevent this
from working.&lt;/p&gt;
&lt;p&gt;A much better approach is to create a unique version for each deployed change
and then track that version in your analytics tool. Most analytics tools support
setting a version. If yours does not, you can create a custom dimension and set
that dimension to your deployed version.&lt;/p&gt;
&lt;h3 id=&quot;run-experiments&quot;&gt;Run experiments &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#run-experiments&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can take versioning one step further by tracking multiple versions (or
experiments) at the same time.&lt;/p&gt;
&lt;p&gt;If your analytics tool lets you define experiment groups, use that feature.
Otherwise, you can use custom dimensions to ensure each of your metric values
can be associated with a particular experiment group in your reports.&lt;/p&gt;
&lt;p&gt;With experimentation in place in your analytics, you can roll out an
experimental change to a subset of your users and compare the performance of
that change to the performance of users in the control group. Once you have
confidence that a change does indeed improve performance, you can roll it out to
all users.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Experiment groups should always be set on the server. Avoid using any experimentation or A/B testing tool that runs on the client. These tools will typically block rendering until a user&#39;s experiment group is determined, which can be detrimental to your LCP times. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;ensure-measurement-doesnt-affect-performance&quot;&gt;Ensure measurement doesn&#39;t affect performance &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#ensure-measurement-doesnt-affect-performance&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When measuring performance on real users, it&#39;s absolutely critical that any
performance measurement code you&#39;re running does not negatively impact the
performance of your page. If it does, then any conclusions you attempt to draw
on how your performance affects your business will be unreliable, as you&#39;ll
never know if the presence of the analytics code itself is having the largest
negative impact.&lt;/p&gt;
&lt;p&gt;Always follow these principles when deploying RUM analytics code on your
production site:&lt;/p&gt;
&lt;h3 id=&quot;defer-your-analytics&quot;&gt;Defer your analytics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#defer-your-analytics&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Analytics code should always be loaded in an asynchronous, non-blocking way, and
generally it should be loaded last. If you load your analytics code in a
blocking way, it can negatively affect LCP.&lt;/p&gt;
&lt;p&gt;All of the APIs used to measure the Core Web Vitals metrics were specifically
designed to support asynchronous and deferred script loading (via the
&lt;a href=&quot;https://www.chromestatus.com/feature/5118272741572608&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;buffered&lt;/code&gt;&lt;/a&gt; flag), so
there&#39;s no need to rush to get your scripts loaded early.&lt;/p&gt;
&lt;p&gt;In the event that you&#39;re measuring a metric that cannot be computed later in the
page load timeline, you should inline &lt;em&gt;only&lt;/em&gt; the code that needs to run early
into the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of your document (so it&#39;s not a &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources/&quot; rel=&quot;noopener&quot;&gt;render-blocking
request&lt;/a&gt;) and defer the rest. Do not load all your
analytics early just because a single metric requires it.&lt;/p&gt;
&lt;h3 id=&quot;do-not-create-long-tasks&quot;&gt;Do not create long tasks &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#do-not-create-long-tasks&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Analytics code often runs in response to user input, but if your analytics code
is conducting a lot of DOM measurements or using other processor-intensive APIs
the analytics code itself can cause poor input responsiveness. In addition, if
the JavaScript file containing your analytics code is large, executing that file
can block the main thread and negatively affect &lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt; or &lt;a href=&quot;https://web.dev/inp/&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;use-non-blocking-apis&quot;&gt;Use non-blocking APIs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#use-non-blocking-apis&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;APIs like
&lt;code&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/sendBeacon&quot; rel=&quot;noopener&quot;&gt;sendBeacon()&lt;/a&gt;&lt;/code&gt;
and
&lt;code&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/requestIdleCallback&quot; rel=&quot;noopener&quot;&gt;requestIdleCallback()&lt;/a&gt;&lt;/code&gt;
are specifically designed for running non-critical tasks in a way that doesn&#39;t
block user-critical tasks.&lt;/p&gt;
&lt;p&gt;These APIs are great tools to use in a RUM analytics library.&lt;/p&gt;
&lt;p&gt;In general, all analytics beacons should be sent using the &lt;code&gt;sendBeacon()&lt;/code&gt; API
(if available), and all passive analytics measurement code should be run during
idle periods.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; For guidance on how to maximize the use of idle time, while still ensuring code can be run urgently when needed (like when a user is unloading the page), refer to the &lt;a href=&quot;https://philipwalton.com/articles/idle-until-urgent/&quot;&gt;idle-until-urgent&lt;/a&gt; pattern. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;dont-track-more-than-what-you-need&quot;&gt;Don&#39;t track more than what you need &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals-field-measurement-best-practices/#dont-track-more-than-what-you-need&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The browser exposes a lot of performance data, but just because the data is
available does not necessarily mean you should record it and send it to your
analytics servers.&lt;/p&gt;
&lt;p&gt;For example, the &lt;a href=&quot;https://w3c.github.io/resource-timing/&quot; rel=&quot;noopener&quot;&gt;Resource Timing API&lt;/a&gt;
provides detailed timing data for every single resource loaded on your page.
However, it&#39;s unlikely that all of that data will be necessarily or useful in
improving resource load performance.&lt;/p&gt;
&lt;p&gt;In short, don&#39;t just track data because it&#39;s there, ensure the data will be used
before consuming resources tracking it.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>Optimize Largest Contentful Paint</title>
    <link href="https://web.dev/optimize-lcp/"/>
    <updated>2020-05-05T00:00:00Z</updated>
    <id>https://web.dev/optimize-lcp/</id>
    <content type="html" mode="escaped">&lt;p&gt;&lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt; is one of the three &lt;a href=&quot;https://web.dev/vitals/#core-web-vitals&quot;&gt;Core Web Vitals&lt;/a&gt; metrics, and it represents how quickly the main content of a web page is loaded. Specifically, LCP measures the time from when the user initiates loading the page until the largest image or text block is rendered within the viewport.&lt;/p&gt;
&lt;p&gt;To provide a good user experience, &lt;strong&gt;sites should strive to have an LCP of 2.5 seconds or less for at least 75% of page visits.&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;
  &lt;picture&gt;
    &lt;source srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/elqsdYqQEefWJbUM2qMO.svg&quot; media=&quot;(min-width: 640px)&quot; width=&quot;800&quot; height=&quot;200&quot; /&gt;
    &lt;img alt=&quot;Good LCP values are 2.5 seconds or less, poor values are greater than 4.0 seconds, and anything in between needs improvement&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8ZW8LQsagLih1ZZoOmMR.svg&quot; width=&quot;640&quot; /&gt;
  &lt;/picture&gt;
&lt;/figure&gt;
&lt;p&gt;There are a number of factors that can affect how quickly the browser is able to load and render a web page, and delays across any of them can have a significant impact on LCP.&lt;/p&gt;
&lt;p&gt;It&#39;s rare that a quick fix to a single part of a page will result in a meaningful improvement to LCP. To improve LCP you have to look at the entire loading process and make sure every step along the way is optimized.&lt;/p&gt;
&lt;h2 id=&quot;understanding-your-lcp-metric&quot;&gt;Understanding your LCP metric &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#understanding-your-lcp-metric&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before optimizing LCP, developers should seek to understand if they even have an LCP issue, and the extent of any such issue.&lt;/p&gt;
&lt;p&gt;LCP can be measured in a number of tools and not all of these measure LCP in the same way. To understand LCP of real users, we should look at what real users are experiencing, rather than what a lab-based tool like &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt; or local testing shows. These lab-based tools can give a wealth of information to explain and help you improve LCP, but be aware that lab tests alone may not be entirely representative of what your actual users experience.&lt;/p&gt;
&lt;p&gt;LCP data based on real users can be surfaced from Real User Monitoring (RUM) tools installed on a site, or via the &lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience Report (CrUX)&lt;/a&gt; which collect anonymous data from real Chrome users for millions of websites.&lt;/p&gt;
&lt;h3 id=&quot;using-pagespeed-insights-crux-lcp-data&quot;&gt;Using PageSpeed Insights CrUX LCP data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#using-pagespeed-insights-crux-lcp-data&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed Insights&lt;/a&gt; provides access to CrUX data in the top section labeled &lt;strong&gt;Discover what your real users are experiencing&lt;/strong&gt;. More detailed lab-based data is available in the bottom section labeled &lt;strong&gt;Diagnose performance issues&lt;/strong&gt;. If CrUX data is available for your website, always concentrate on the real user data first.&lt;/p&gt;
&lt;img alt=&quot;CrUX data shown in PageSpeed Insights&quot; decoding=&quot;async&quot; height=&quot;478&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/enRFus2GE24gvchY9fdV.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Where CrUX does not provide data (for example, a page with insufficient traffic to get page-level data), CrUX should be supplemented with RUM data collected using JavaScript APIs running on the web page. This can also provide much more data than CrUX can expose as a public dataset. Later in this guide we will explain how to collect this data using JavaScript. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;PageSpeed Insights shows up to four different CrUX data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mobile&lt;/strong&gt; data for &lt;strong&gt;This URL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desktop&lt;/strong&gt; data for &lt;strong&gt;This URL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile&lt;/strong&gt; data for the whole &lt;strong&gt;Origin&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desktop&lt;/strong&gt; data for the whole &lt;strong&gt;Origin&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These can be toggled in the controls at the top, and top right-hand side of this section. Be aware that where a URL does not have sufficient data to be shown at the URL level—but does have data for the origin—PageSpeed Insights will automatically show this.&lt;/p&gt;
&lt;img alt=&quot;PageSpeed Insight&amp;#x27;s falling back to origin-level data where url-level data is not available&quot; decoding=&quot;async&quot; height=&quot;295&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/wWQxf1DbjElItJTcJsBn.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;The LCP for the whole origin may be very different to an individual page&#39;s LCP depending on how the LCP is loaded on that page compared to other pages on that origin. It can also be affected by how visitors navigate to these pages. Home pages tend to be visted by new users and so may often be loaded &amp;quot;cold&amp;quot;, without any cached content and so are often the slowest pages on a website.&lt;/p&gt;
&lt;p&gt;Looking at the four different categories of CrUX data can help you understand whether an LCP issue is specific to this page, or a more general site-wide issue. Similarly, it can show which device types have LCP issues.&lt;/p&gt;
&lt;h3 id=&quot;using-pagespeed-insights-crux-supplementary-metrics&quot;&gt;Using PageSpeed Insights CrUX supplementary metrics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#using-pagespeed-insights-crux-supplementary-metrics&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Those looking to optimize LCP should also use the &lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/ttfb/&quot;&gt;Time to First Byte (TTFB)&lt;/a&gt; timings, which are good diagnostic metrics that can provide valuable insights into LCP.&lt;/p&gt;
&lt;p&gt;TTFB is the time when the visitor starting to navigate to a page (for example, clicking on a link), until the first bytes of the HTML document are received. A high TTFB can make achieving a 2.5 second LCP challenging, or even impossible.&lt;/p&gt;
&lt;p&gt;A high TTFB can be due to mutiple server redirects, visitors located far away from the nearest site server, visitors on poor network conditions, or an inability to use cached content due to query parameters.&lt;/p&gt;
&lt;p&gt;Once a page starts rendering, there may be an initial paint (for example, the background color), followed by some content appearing (for example, the site header). The appearance of the initial content is measured by FCP. The delta between FCP and other metrics can be very telling.&lt;/p&gt;
&lt;p&gt;A large delta between TTFB and FCP could indicate that the browser needs to download a lot of render-blocking assets. It can also be a sign it must complete a lot of work to render any meaningful content—a classic sign of a site that relies heavily on client-side rendering.&lt;/p&gt;
&lt;p&gt;A large delta between FCP and LCP indicates that the LCP resource is either not immediately available for the browser to prioritize (for example, text or images that are managed by JavaScript rather than being available in the initial HTML), or that the browser is completing other work before it can display the LCP content.&lt;/p&gt;
&lt;h3 id=&quot;using-pagespeed-insights-lighthouse-data&quot;&gt;Using PageSpeed Insights Lighthouse data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#using-pagespeed-insights-lighthouse-data&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Lighthouse section of PageSpeed Insights offers some guidance to improving LCP, but first you should check if the LCP given is broadly inline with real user data provided by CrUX.&lt;/p&gt;
&lt;p&gt;If Lighthouse is showing no LCP issue, but the CrUX data is, then any Lighthouse suggestions may not be relevant. The opposite is also true—if Lighthouse is showing a really poor LCP time, but CrUX data is showing your users mostly have a good LCP, then you may wish to consider the priority of optimizing LCP further, or if time is better spent on other performance improvements. Also be sure to check that the CrUX data is for this page and not for the full origin as detailed above.&lt;/p&gt;
&lt;p&gt;If both sources of data are showing an LCP that should be improved than the Lighthouse section can provide valuable guidance on ways to improve LCP. Use the LCP filter to only show audits relevant to LCP:&lt;/p&gt;
&lt;img alt=&quot;Lighthouse LCP Opportunities and Diagnostics&quot; decoding=&quot;async&quot; height=&quot;498&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/himAvC1GMr7K0kcbvG4F.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;As well as the &lt;strong&gt;Opportunities&lt;/strong&gt; to improve, there is &lt;strong&gt;Diagnostic&lt;/strong&gt; information that may provide more information to help diagnose the issue. The &lt;strong&gt;Largest Contentful Paint element&lt;/strong&gt; diagnostic shows a useful breakdown of the various timings that made up the LCP:&lt;/p&gt;
&lt;img alt=&quot;Lighthouse LCP phases&quot; decoding=&quot;async&quot; height=&quot;474&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/KhIuP2CNToNeGa33w48K.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;We will delve into these sub-parts next.&lt;/p&gt;
&lt;h2 id=&quot;lcp-breakdown&quot;&gt;LCP breakdown &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#lcp-breakdown&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Optimizing for LCP can be a more complex task when PageSpeed Insights does not give you the answer on how to improve this metric. With complex tasks it&#39;s generally better to break them down into smaller, more manageable tasks and address each separately. This guide will present a methodology for how to break down LCP into its most critical sub-parts and then present specific recommendations and best practices for how to optimize each part.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; For a visual overview of the context presented in this section, see &lt;a href=&quot;https://youtu.be/fWoI9DXmpdk&quot;&gt;A Deep Dive into Optimizing LCP&lt;/a&gt; from Google I/O 2022: &lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;fWoI9DXmpdk&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Most page loads typically include a number of network requests, but for the purposes of identifying opportunities to improve LCP, you should start by looking at just two:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The initial HTML document&lt;/li&gt;
&lt;li&gt;The LCP resource (if applicable)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While other requests on the page can affect LCP, these two requests—specifically the times when the LCP resource begins and ends—reveal whether or not your page is optimized for LCP.&lt;/p&gt;
&lt;p&gt;To identify the LCP resource, you can use developer tools (such as PageSpeed Insights discussed above, &lt;a href=&quot;https://developer.chrome.com/docs/devtools/&quot; rel=&quot;noopener&quot;&gt;Chrome DevTools&lt;/a&gt;, or &lt;a href=&quot;https://webpagetest.org/&quot; rel=&quot;noopener&quot;&gt;WebPageTest&lt;/a&gt;) to determine the &lt;a href=&quot;https://web.dev/lcp/#what-elements-are-considered&quot;&gt;LCP element&lt;/a&gt;. From there, you can match the URL (again, if applicable) loaded by the element on a &lt;a href=&quot;https://developer.chrome.com/docs/devtools/network/reference/&quot; rel=&quot;noopener&quot;&gt;network waterfall&lt;/a&gt; of all resources loaded by the page.&lt;/p&gt;
&lt;p&gt;For example, the following visualization shows these resources highlighted on a network waterfall diagram from a typical page load, where the LCP element requires an image request to render.&lt;/p&gt;
&lt;img alt=&quot;A network waterfall with the HTML and LCP resources highlighted&quot; decoding=&quot;async&quot; height=&quot;375&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/96YFhl0GQYIKFnJeOsVW.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;For a well-optimized page, you want your LCP resource request to start loading as early as it can, and you want the LCP element to render as quickly as possible after the LCP resource finishes loading. To help visualize whether or not a particular page is following this principle, you can break down the total LCP time into the following sub-parts:&lt;/p&gt;
&lt;img alt=&quot;A breakdown of LCP showing the four individual sub-parts&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/4AriEgko87GR1iZSgOou.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;This table explains each of these LCP sub-parts in more detail:&lt;/p&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;LCP sub-part&lt;/th&gt;
        &lt;th&gt;Description&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;Time to first byte (TTFB)&lt;/td&gt;
        &lt;td&gt;The time from when the user initiates loading the page until when the browser receives the first byte of the HTML document response. (See the &lt;a href=&quot;https://web.dev/ttfb/&quot;&gt;TTFB&lt;/a&gt; metric doc for more details.)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Resource load delay&lt;/td&gt;
        &lt;td&gt;The delta between TTFB and when the browser starts loading the LCP resource. &lt;em&gt;*&lt;/em&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Resource load time&lt;/td&gt;
        &lt;td&gt;The time it takes to load the LCP resource itself. &lt;em&gt;*&lt;/em&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Element render delay&lt;/td&gt;
        &lt;td&gt;The delta between when the LCP resource finishes loading until the LCP element is fully rendered.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;caption&gt;* If the LCP element does not require a resource load to render (for example, if the element is a text node rendered with a system font), this time will be 0.&lt;/caption&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Every single page can have its LCP value broken down into these four sub-parts. There is no overlap or gap between them, and collectively they add up to the full LCP time.&lt;/p&gt;
&lt;p&gt;When optimizing LCP, it&#39;s helpful to try to optimize these sub-parts individually. But it&#39;s also important to keep in mind that you need to optimize all of them. In some cases, an optimization applied to one part will not improve LCP, it will just shift the time saved to another part.&lt;/p&gt;
&lt;p&gt;For example, in the earlier network waterfall, if you reduced the file size of our image by compressing it more or switching to a more optimal format (such as AVIF or WebP), that would reduce the &lt;strong&gt;resource load time&lt;/strong&gt;, but it would not actually improve LCP because the time would just shift to the &lt;strong&gt;element render delay&lt;/strong&gt; sub-part:&lt;/p&gt;
&lt;img alt=&quot;The same breakdown of LCP shown earlier where the resource load time sub-part is shortened but the overall LCP time remains the same.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/M3JgFnahW8pPb9o1lf2r.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;The reason this happens is because, on this page, the LCP element is hidden until the JavaScript code finishes loading, and then everything is revealed at once.&lt;/p&gt;
&lt;p&gt;This example helps illustrate the point that you need to optimize all of these sub-parts in order to achieve the best LCP outcomes.&lt;/p&gt;
&lt;h3 id=&quot;optimal-sub-part-times&quot;&gt;Optimal sub-part times &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#optimal-sub-part-times&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In order to optimize each sub-part of LCP, it&#39;s important to understand what the ideal breakdown of these sub-parts is on a well-optimized page.&lt;/p&gt;
&lt;p&gt;Of the four sub-parts, two have the word &amp;quot;delay&amp;quot; in their names. That is a clue that you want to get these times as close to zero as possible. The other two parts involve network requests, which by their very nature take time.&lt;/p&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;LCP sub-part&lt;/th&gt;
        &lt;th&gt;% of LCP&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;Time to first byte (TTFB)&lt;/td&gt;
        &lt;td&gt;~40%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Resource load delay&lt;/td&gt;
        &lt;td&gt;&amp;lt;10%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Resource load time&lt;/td&gt;
        &lt;td&gt;~40%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Element render delay&lt;/td&gt;
        &lt;td&gt;&amp;lt;10%&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;TOTAL&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Note that these time breakdowns are not strict rules, they&#39;re guidelines. If the LCP times on your pages are consistently within 2.5 seconds, then it doesn&#39;t really matter what the relative proportions are. But if you&#39;re spending a lot of unnecessary time in either of the &amp;quot;delay&amp;quot; portions, then it will be very difficult to constantly hit the &lt;a href=&quot;https://web.dev/lcp/#what-is-a-good-lcp-score&quot;&gt;2.5 second target&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A good way to think about the breakdown of LCP time is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;vast majority&lt;/strong&gt; of the LCP time should be spent loading the HTML document and LCP source.&lt;/li&gt;
&lt;li&gt;Any time before LCP where one of these two resources is &lt;em&gt;not&lt;/em&gt; loading is &lt;strong&gt;an opportunity to improve&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Given the &lt;a href=&quot;https://web.dev/lcp/#what-is-a-good-lcp-score&quot;&gt;2.5 second target&lt;/a&gt; for LCP, it may be tempting to try to convert these percentages into absolute numbers, but that is not recommended. These sub-parts are only meaningful relative to each other, so it&#39;s best to always measure them that way. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;how-to-optimize-each-part&quot;&gt;How to optimize each part &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#how-to-optimize-each-part&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that you understand how each of the LCP sub-part times should break down on a well-optimized page, you can start optimizing your own pages.&lt;/p&gt;
&lt;p&gt;The next four sections will present recommendations and best practices for how to optimize each part. They&#39;re presented in order, starting with the optimizations that are likely to have the biggest impact.&lt;/p&gt;
&lt;h3 id=&quot;1-eliminate-resource-load-delay&quot;&gt;1. Eliminate &lt;em&gt;resource load delay&lt;/em&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#1-eliminate-resource-load-delay&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The goal in this step is to ensure the LCP resource starts loading as early as possible. While in theory the earliest a resource &lt;em&gt;could&lt;/em&gt; start loading is immediately after TTFB, in practice there is always some delay before browsers actually start loading resources.&lt;/p&gt;
&lt;p&gt;A good rule of thumb is that your LCP resource should start loading at the same time as the first resource loaded by that page. Or, to put that another way, if the LCP resource starts loading later than the first resource, then there&#39;s opportunity for improvement.&lt;/p&gt;
&lt;img alt=&quot;A network waterfall diagram showing the LCP resource starting after the first resource, showing the opportunity for improvement&quot; decoding=&quot;async&quot; height=&quot;375&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8mqXeiEsLQwjgq2lEbjn.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Generally speaking, there are two factors that affect how quickly an LCP resource can be loading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When the resource is discovered.&lt;/li&gt;
&lt;li&gt;What priority the resource is given.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;optimize-when-the-resource-is-discovered&quot;&gt;Optimize when the resource is discovered &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#optimize-when-the-resource-is-discovered&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To ensure your LCP resource starts loading as early as possible, it&#39;s critical that the resource is discoverable in the initial HTML document response by the browser&#39;s &lt;a href=&quot;https://web.dev/preload-scanner/&quot;&gt;preload scanner&lt;/a&gt;. For example, in the following cases, the browser can discover the LCP resource by scanning the HTML document response:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The LCP element is an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element, and its &lt;code&gt;src&lt;/code&gt; or &lt;code&gt;srcset&lt;/code&gt; attributes are present in the initial HTML markup.&lt;/li&gt;
&lt;li&gt;The LCP element requires a CSS background image, but that image is preloaded via &lt;code&gt;&amp;lt;link rel=&amp;quot;preload&amp;quot;&amp;gt;&lt;/code&gt; in the HTML markup (or via a &lt;code&gt;Link&lt;/code&gt; header).&lt;/li&gt;
&lt;li&gt;The LCP element is a text node that requires a web font to render, and the font is loaded via &lt;code&gt;&amp;lt;link rel=&amp;quot;preload&amp;quot;&amp;gt;&lt;/code&gt; in the HTML markup (or via a &lt;code&gt;Link&lt;/code&gt; header).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some examples where the LCP resource cannot be discovered from scanning the HTML document response:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The LCP element is an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; that is dynamically added to the page via JavaScript.&lt;/li&gt;
&lt;li&gt;The LCP element is lazily loaded with a JavaScript library that hides its &lt;code&gt;src&lt;/code&gt; or &lt;code&gt;srcset&lt;/code&gt; attributes (often as &lt;code&gt;data-src&lt;/code&gt; or &lt;code&gt;data-srcset&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The LCP element requires a CSS background image.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In each of these cases, the browser needs to run the script or apply the stylesheet—which usually involves waiting for network requests to finish—before it can discover the LCP resource and could start loading it. This is never optimal.&lt;/p&gt;
&lt;p&gt;To eliminate unnecessary resource load delay, your LCP resource should &lt;em&gt;always&lt;/em&gt; be discoverable from the HTML source. In cases where the resource is only referenced from an external CSS or JavaScript file, then the LCP resource should be preloaded, with a high fetch priority (more on &lt;a href=&quot;https://web.dev/optimize-lcp/#optimize-the-priority-the-resource-is-given&quot;&gt;fetch priority in the next section&lt;/a&gt;); for example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Load the stylesheet that will reference the LCP image. --&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/path/to/styles.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Preload the LCP image with a high fetchpriority so it starts loading with the stylesheet. --&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;preload&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;fetchpriority&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;high&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/path/to/hero-image.webp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image/webp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; On most pages, ensuring that the LCP resource starts loading at the same time as the first resource is good enough, but be aware that it is possible to construct a page where none of the resources are discovered early and all of them start loading significantly later than TTFB. So while comparing with the first resource is a good way to identify opportunities to improve, it may not be sufficient in some cases, so it&#39;s still important to measure this time relative to TTFB and ensure it remains small. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;optimize-the-priority-the-resource-is-given&quot;&gt;Optimize the priority the resource is given &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#optimize-the-priority-the-resource-is-given&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Even if the LCP resource is discoverable from the HTML markup, it &lt;em&gt;still&lt;/em&gt; may not start loading as early as the first resource. This can happen if the browser preload scanner&#39;s priority heuristics do not recognize that the resource is important, or if it determines that other resources are more important.&lt;/p&gt;
&lt;p&gt;For example, you can delay your LCP image via HTML if you set &lt;a href=&quot;https://web.dev/browser-level-image-lazy-loading/&quot;&gt;&lt;code&gt;loading=&amp;quot;lazy&amp;quot;&lt;/code&gt;&lt;/a&gt; on your &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element. Using lazy loading means that the resource will not be loaded until after layout confirms the image is in the viewport and so may begin loading later than it otherwise would.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Never lazy-load your LCP image, as that will always lead to unnecessary resource load delay, and will have a negative impact on LCP. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Even without lazy loading, images are not initially loaded with the highest priority by browsers as they are not render-blocking resources. You can hint to the browser as to which resources are most important via the &lt;a href=&quot;https://web.dev/fetch-priority/&quot;&gt;&lt;code&gt;fetchpriority&lt;/code&gt;&lt;/a&gt; attribute for resources that could benefit from a higher priority:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;fetchpriority&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;high&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/path/to/hero-image.webp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;It&#39;s a good idea to set &lt;code&gt;fetchpriority=&amp;quot;high&amp;quot;&lt;/code&gt; on an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element if you think it&#39;s likely to be your page&#39;s LCP element—but limit this to just one or two images (based on common desktop and mobile viewport sizes), otherwise the signal becomes meaningless. You can also lower the priority of images that may be early in the document response, but isn&#39;t visible due to styling, such as images in carousel slides that are not visible at startup:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;fetchpriority&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;low&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/path/to/carousel-slide-3.webp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Deprioritizing certain resources can afford more bandwidth to resources that need it more—but be careful. Always check resource priority in DevTools and test changes with lab and field tools.&lt;/p&gt;
&lt;p&gt;After you have optimized your LCP resource priority and discovery time, your network waterfall should look like this (with the LCP resource starting at the same time as the first resource):&lt;/p&gt;
&lt;img alt=&quot;A network waterfall diagram showing the LCP resource now starting at the same time as the first resource&quot; decoding=&quot;async&quot; height=&quot;375&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/f9s7SJSBNKcMm3VmcvtT.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Another reason your LCP resource may not start loading as early as possible—even when it&#39;s discoverable from the HTML source—is if it&#39;s hosted on a different origin, as these requests require the browser to connect to that origin before the resource can start loading. When possible, it&#39;s a good idea to host critical resources on the same origin as your HTML document resource because then those resources can save time by reusing the existing connection (more on this point later). &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;2-eliminate-element-render-delay&quot;&gt;2. Eliminate &lt;em&gt;element render delay&lt;/em&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#2-eliminate-element-render-delay&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The goal in this step is to ensure the LCP element can render &lt;em&gt;immediately&lt;/em&gt; after its resource has finished loading, no matter when that happens.&lt;/p&gt;
&lt;p&gt;The primary reason the LCP element &lt;em&gt;wouldn&#39;t&lt;/em&gt; be able to render immediately after its resource finishes loading is if rendering is &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources/&quot; rel=&quot;noopener&quot;&gt;blocked&lt;/a&gt; for some other reason:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rendering of the entire page is blocked due to stylesheets or synchronous scripts in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; that are still loading.&lt;/li&gt;
&lt;li&gt;The LCP resource has finished loading, but the LCP element has not yet been added to the DOM (it&#39;s waiting for some JavaScript code to load).&lt;/li&gt;
&lt;li&gt;The element is being hidden by some other code, such as an A/B testing library that&#39;s still determining what experiment the user should be in.&lt;/li&gt;
&lt;li&gt;The main thread is blocked due to &lt;a href=&quot;https://web.dev/long-tasks-devtools/#what-are-long-tasks&quot;&gt;long tasks&lt;/a&gt;, and rendering work needs to wait until those long tasks complete.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following sections explain how to address the most common causes of unnecessary element render delay.&lt;/p&gt;
&lt;h4 id=&quot;reduce-or-inline-render-blocking-stylesheets&quot;&gt;Reduce or inline render-blocking stylesheets &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#reduce-or-inline-render-blocking-stylesheets&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Stylesheets loaded from the HTML markup will block rendering of all content that follows them, which is good, since you generally do not want to render unstyled HTML. However, if the stylesheet is so large that it takes significantly longer to load than the LCP resource, then it will prevent the LCP element from rendering—even after its resource has finished loading, as shown in this example:&lt;/p&gt;
&lt;img alt=&quot;A network waterfall diagram showing a large CSS file blocking rendering of the LCP element because it takes longer to load than the LCP resource&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/A5XTlQxIdF9WsLdXiBXE.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;To fix this, your options are to either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;inline the stylesheet into the HTML to avoid the additional network request; or,&lt;/li&gt;
&lt;li&gt;reduce the size of the stylesheet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In general, inlining your stylesheet is only recommended if your stylesheet is small since inlined content in the HTML cannot benefit from caching in subsequent page loads. If a stylesheet is so large that it takes longer to load than the LCP resource, then it&#39;s unlikely to be a good candidate for inlining.&lt;/p&gt;
&lt;p&gt;In most cases, the best way to ensure the stylesheet does not block rendering of the LCP element is to reduce its size so that it&#39;s smaller than the LCP resource. This should ensure it&#39;s not a bottleneck for most visits.&lt;/p&gt;
&lt;p&gt;Some recommendations to reduce the size of the stylesheet are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/unused-css-rules/&quot; rel=&quot;noopener&quot;&gt;Remove unused CSS&lt;/a&gt;: use Chrome DevTools to find CSS rules that aren&#39;t being used and can potentially be removed (or deferred).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/defer-non-critical-css/&quot;&gt;Defer non-critical CSS&lt;/a&gt;: split your stylesheet out into styles that are required for initial page load and then styles that can be loaded lazily.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/reduce-network-payloads-using-text-compression/&quot;&gt;Minify and compress CSS&lt;/a&gt;: for styles that are critical, make sure you&#39;re reducing their &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming/transferSize&quot; rel=&quot;noopener&quot;&gt;transfer size&lt;/a&gt; as much as possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;defer-or-inline-render-blocking-javascript&quot;&gt;Defer or inline render-blocking JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#defer-or-inline-render-blocking-javascript&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;It is almost never necessary to add synchronous scripts (scripts without the &lt;code&gt;async&lt;/code&gt; or &lt;code&gt;defer&lt;/code&gt; attributes) to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of your pages, and doing so will almost always have a negative impact on performance.&lt;/p&gt;
&lt;p&gt;In cases where JavaScript code needs to run as early as possible in the page load, it&#39;s best to inline it so rendering isn&#39;t delayed waiting on another network request. As with stylesheets, though, you should only inline scripts if they&#39;re very small.&lt;/p&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;worse&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Don&#39;t&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/path/to/main.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;better&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Do&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Inline script contents directly in the HTML.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// IMPORTANT: only do this for very small scripts.&lt;/span&gt;&lt;br /&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;h4 id=&quot;use-server-side-rendering&quot;&gt;Use server-side rendering &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#use-server-side-rendering&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/rendering-on-the-web/#server-rendering&quot;&gt;Server-side rendering&lt;/a&gt; (SSR) is the process of running your client-side application logic on the server and responding to HTML document requests with the full HTML markup.&lt;/p&gt;
&lt;p&gt;From the perspective of optimizing LCP, there are two primary advantage of SSR:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your image resources will be discoverable from the HTML source (as discussed in &lt;a href=&quot;https://web.dev/optimize-lcp/#1-eliminate-resource-load-delay&quot;&gt;step 1&lt;/a&gt; earlier).&lt;/li&gt;
&lt;li&gt;Your page content will not require additional JavaScript requests to finish before it can render.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The main downside of SSR is it requires additional server processing time, which can slow down your TTFB. This trade-off is usually worth it though because server processing times are within your control, whereas the network and device capabilities of your users are not.&lt;/p&gt;
&lt;p&gt;A similar option to SSR is called static site generation (SSG) or &lt;a href=&quot;https://web.dev/rendering-on-the-web/#terminology&quot;&gt;prerendering&lt;/a&gt;. This is the process of generating your HTML pages in a build step rather than on-demand. If prerendering is possible with your architecture, it&#39;s generally a better choice for performance.&lt;/p&gt;
&lt;h4 id=&quot;break-up-long-tasks&quot;&gt;Break up long tasks &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#break-up-long-tasks&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Even if you&#39;ve followed the above advice, and your JavaScript code is not render-blocking nor is it responsible for rendering your elements, it can still delay LCP.&lt;/p&gt;
&lt;p&gt;The most common reason this happens is when pages load large JavaScript files, which need to be parsed and executed on the browser&#39;s main thread. This means that, even if your image resource is fully downloaded, it may still have to wait until an unrelated script finishes executing before it can render.&lt;/p&gt;
&lt;p&gt;All browsers today render images on the main thread, which means anything that blocks the main thread can also lead to unnecessary &lt;em&gt;element render delay&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;3-reduce-resource-load-time&quot;&gt;3. Reduce &lt;em&gt;resource load time&lt;/em&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#3-reduce-resource-load-time&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The goal of this step is to reduce the time spent transferring the bytes of the resource over the network to the user&#39;s device. In general, there are three ways to do that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduce the size of the resource.&lt;/li&gt;
&lt;li&gt;Reduce the distance the resource has to travel.&lt;/li&gt;
&lt;li&gt;Reduce contention for network bandwidth.&lt;/li&gt;
&lt;li&gt;Eliminate the network time entirely.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;reduce-the-size-of-the-resource&quot;&gt;Reduce the size of the resource &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#reduce-the-size-of-the-resource&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The LCP resource of a page (if it has one) will either be an image or a web font. The following guides go into great detail about how to reduce the size of both:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images/&quot; rel=&quot;noopener&quot;&gt;Serve the optimal image size&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-webp-images/&quot; rel=&quot;noopener&quot;&gt;Use modern image formats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-optimized-images/&quot; rel=&quot;noopener&quot;&gt;Compress images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/reduce-webfont-size/&quot;&gt;Reduce web font size&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;reduce-the-distance-the-resource-has-to-travel&quot;&gt;Reduce the distance the resource has to travel &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#reduce-the-distance-the-resource-has-to-travel&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;In addition to reducing the size of a resource, you can also reduce the load times by getting your servers as geographically close to your users as possible. And the best way to do that is to use a &lt;a href=&quot;https://web.dev/content-delivery-networks/&quot;&gt;content delivery network&lt;/a&gt; (CDN).&lt;/p&gt;
&lt;p&gt;In fact, &lt;a href=&quot;https://web.dev/image-cdns/&quot;&gt;image CDNs&lt;/a&gt; in particular are a great choice because they not only reduce the distance the resource has to travel, but they also generally reduce the size of the resource—automatically implementing all of the size-reduction recommendations from earlier for you.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; While image CDNs are a great way to reduce resource load times, using a third-party domain to host your images comes with an additional connection cost. While preconnecting to the origin can mitigate some of this cost, the best option is to serve images from the same origin as your HTML document. Many CDNs allow you to proxy requests from your origin to theirs, which is a great option to look into if available. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;reduce-contention-for-network-bandwidth&quot;&gt;Reduce contention for network bandwidth &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#reduce-contention-for-network-bandwidth&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Even if you&#39;ve reduced the size of your resource and the distance it has to travel, a resource can still take a long time to load if you&#39;re loading many other resources at the same time. This problem is known as &lt;em&gt;network contention&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If you&#39;ve given your LCP resource a &lt;a href=&quot;https://web.dev/fetch-priority/&quot;&gt;high &lt;code&gt;fetchpriority&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://web.dev/optimize-lcp/#1-eliminate-resource-load-delay&quot;&gt;started loading it as soon as possible&lt;/a&gt; then the browser will do its best to prevent lower-priority resources from competing with it. However, if you&#39;re loading many resources with high &lt;code&gt;fetchpriority&lt;/code&gt;, or if you&#39;re just loading a lot of resources in general, then it could affect how quickly the LCP resource loads.&lt;/p&gt;
&lt;h4 id=&quot;eliminate-the-network-time-entirely&quot;&gt;Eliminate the network time entirely &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#eliminate-the-network-time-entirely&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The best way to reduce resource load times is to eliminate the network entirely from the process. If you serve your resources with an &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/&quot; rel=&quot;noopener&quot;&gt;efficient cache-control policy&lt;/a&gt;, then visitors who request those resources a second time will have them served from the cache—bringing the &lt;em&gt;resource load time&lt;/em&gt; to essentially zero!&lt;/p&gt;
&lt;p&gt;And if your LCP resource is a web font, in addition to &lt;a href=&quot;https://web.dev/reduce-webfont-size/&quot;&gt;reducing web font size&lt;/a&gt;, you should also consider whether you need to block rendering on the web font resource load. If you set a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/@font-face/font-display&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;font-display&lt;/code&gt;&lt;/a&gt; value of anything other than &lt;code&gt;auto&lt;/code&gt; or &lt;code&gt;block&lt;/code&gt;, then text will &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/font-display/&quot; rel=&quot;noopener&quot;&gt;always be visible during load&lt;/a&gt;, and LCP will not be blocked on an additional network request.&lt;/p&gt;
&lt;p&gt;Finally, if your LCP resource is small, it may make sense to inline the resources as a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs&quot; rel=&quot;noopener&quot;&gt;data URL&lt;/a&gt;, which will also eliminate the additional network request. However, using data URLs &lt;a href=&quot;https://calendar.perfplanet.com/2018/performance-anti-patterns-base64-encoding/&quot; rel=&quot;noopener&quot;&gt;comes with caveats&lt;/a&gt; because then the resources cannot be cached and in some cases can lead to longer render delays because of the additional &lt;a href=&quot;https://www.catchpoint.com/blog/data-uri&quot; rel=&quot;noopener&quot;&gt;decode cost&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;4-reduce-time-to-first-byte&quot;&gt;4. Reduce &lt;em&gt;time to first byte&lt;/em&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#4-reduce-time-to-first-byte&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The goal of this step is to deliver the initial HTML as quickly as possible. This step is listed last because it&#39;s often the one developers have the least control over. However, it&#39;s also one of the most important steps because it directly affects every step that comes after it. Nothing can happen on the frontend until the backend delivers that first byte of content, so anything you can do to speed up your TTFB will improve every other load metric as well.&lt;/p&gt;
&lt;p&gt;A common cause of a slow TTFB for an otherwise fast site is making visitors wade through several redirects before finally arriving at the final URL. This can happen when you have visitors from advertisements, or via URL shorteners. Always try to minimize the number of redirects a visitor must wait through.&lt;/p&gt;
&lt;p&gt;Another common cause is when cached content cannot be used from a CDN edge server, and all requests must be directed all the way back to the origin server. This can happen if unique URL parameters are used by visitors for analytics—even if they do not result in different pages.&lt;/p&gt;
&lt;p&gt;For specific guidance on this topic, see &lt;a href=&quot;https://web.dev/optimize-ttfb/&quot;&gt;Optimize TTFB&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;monitor-lcp-breakdown-in-javascript&quot;&gt;Monitor LCP breakdown in JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#monitor-lcp-breakdown-in-javascript&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The timing information for all of the LCP sub-parts discussed above is available to you in JavaScript through a combination of the following performance APIs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/largest-contentful-paint/&quot; rel=&quot;noopener&quot;&gt;Largest Contentful Paint API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/navigation-timing-2/&quot; rel=&quot;noopener&quot;&gt;Navigation Timing API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/resource-timing-2/&quot; rel=&quot;noopener&quot;&gt;Resource Timing API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The benefit to computing these timing values in JavaScript is it allows you to send them to an analytics provider or log them to your developer tools to help with debugging and optimizing.&lt;/p&gt;
&lt;p&gt;For example, the following screenshot uses the &lt;code&gt;performance.measure()&lt;/code&gt; method from the &lt;a href=&quot;https://w3c.github.io/user-timing/&quot; rel=&quot;noopener&quot;&gt;User Timing API&lt;/a&gt; to add bars to the Timings track in the Chrome DevTools Performance panel.&lt;/p&gt;
&lt;img alt=&quot;User Timing measures of the LCP sub-parts visualized in Chrome DevTools&quot; decoding=&quot;async&quot; height=&quot;471&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/swFmxNjI5nLagUOrIjZo.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Visualizations in the timings track are particularly helpful when looked at alongside the Network and Main thread tracks, because you can see at a glance what else is happening on the page during these timespans.&lt;/p&gt;
&lt;p&gt;In addition to visualizing the LCP sub-parts in the timings track, you can also use JavaScript to compute what percentage each sub-part is of the total LCP time. With that information, you can determine whether your pages are meeting the &lt;a href=&quot;https://web.dev/optimize-lcp/#optimal-sub-part-times&quot;&gt;recommended percentage breakdowns&lt;/a&gt; described earlier.&lt;/p&gt;
&lt;p&gt;This screenshot shows an example that logs the total time of each LCP sub-part, as well as its percentage of the total LCP time to the console.&lt;/p&gt;
&lt;img alt=&quot;The LCP sub-part times, as well as their percent of LCP, printed to the console&quot; decoding=&quot;async&quot; height=&quot;267&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/zCB4PVlHUX9Iz5NSwdMu.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Both of these visualizations were created with the following code:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;LCP_SUB_PARTS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;Time to first byte&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;Resource load delay&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;Resource load time&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;Element render delay&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lcpEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; navEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntriesByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lcpResEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; performance&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntriesByType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;resource&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; lcpEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Ignore LCP entries that aren&#39;t images to reduce DevTools noise.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Comment this line out if you want to include text entries.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;lcpEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Compute the start and end times of each LCP sub-part.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// WARNING! If your LCP resource is loaded cross-origin, make sure to add&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// the `Timing-Allow-Origin` (TAO) header to get the most accurate results.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ttfb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; navEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responseStart&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lcpRequestStart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    ttfb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Prefer `requestStart` (if TOA is set), otherwise use `startTime`.&lt;/span&gt;&lt;br /&gt;    lcpResEntry &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; lcpResEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;requestStart &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; lcpResEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startTime &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lcpResponseEnd &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    lcpRequestStart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    lcpResEntry &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; lcpResEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responseEnd &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lcpRenderTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    lcpResponseEnd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Use LCP startTime (which is the final LCP time) as sometimes&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// slight differences between loadTime/renderTime and startTime&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// due to rounding precision.&lt;/span&gt;&lt;br /&gt;    lcpEntry &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; lcpEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startTime &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Clear previous measures before making new ones.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Note: due to a bug this does not work in Chrome DevTools.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token constant&quot;&gt;LCP_SUB_PARTS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;part&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clearMeasures&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;part&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Create measures for each LCP sub-part for easier&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// visualization in the Chrome DevTools Performance panel.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lcpSubPartMeasures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;measure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LCP_SUB_PARTS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ttfb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;measure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LCP_SUB_PARTS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ttfb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lcpRequestStart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;measure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LCP_SUB_PARTS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lcpRequestStart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lcpResponseEnd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;measure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;LCP_SUB_PARTS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lcpResponseEnd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lcpRenderTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Log helpful debug information to the console.&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;LCP value: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lcpRenderTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;LCP element: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lcpEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lcpEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    lcpSubPartMeasures&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;measure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token string-property property&quot;&gt;&#39;LCP sub-part&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; measure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token string-property property&quot;&gt;&#39;Time (ms)&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; measure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;duration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token string-property property&quot;&gt;&#39;% of LCP&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;br /&gt;        Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; measure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;duration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; lcpRenderTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;largest-contentful-paint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can use this code as-is for local debugging, or modify it to send this data to an analytics provider so you can get a better understanding of what the breakdown of LCP is on your pages for real users.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; &lt;p&gt; The above code works for standard navigations, but extra consideration must be taken for &lt;a href=&quot;https://developer.chrome.com/blog/prerender-pages/&quot;&gt;prerendered pages&lt;/a&gt;, which should be measured from the activation start time but which has been kept out of this code for simplicity. &lt;/p&gt; &lt;p&gt; The &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;web-vitals library&lt;/a&gt; includes this breakdown in the attribution build, and includes these considerations. &lt;/p&gt; &lt;p&gt; For those looking to implement their own solution, the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/blob/main/src/attribution/onLCP.ts&quot;&gt;code for this is open source&lt;/a&gt; and is similar to above but with extra lofic for activation start. &lt;/p&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;monitor-lcp-breakdown-via-the-web-vitals-extension&quot;&gt;Monitor LCP breakdown via the Web Vitals extension &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#monitor-lcp-breakdown-via-the-web-vitals-extension&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma&quot; rel=&quot;noopener&quot;&gt;Web Vitals extension&lt;/a&gt; will &lt;a href=&quot;https://web.dev/debug-cwvs-with-web-vitals-extension/#lcp-debug-information&quot;&gt;log the LCP time, LCP element, and these four sub-parts in the console logging&lt;/a&gt;, to easily allow you to see this breakdown.&lt;/p&gt;
&lt;img alt=&quot;Screenshot of the console logging of the Web Vitals extension showing the LCP sub-part timings&quot; decoding=&quot;async&quot; height=&quot;239&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/FEFEkgKuC6RxVh7MHmkn.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/optimize-lcp/#summary&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;LCP is complex, and its timing can be affected by a number of factors. But if you consider that optimizing LCP is primarily about optimizing the load of the LCP resource, it can significantly simplify things.&lt;/p&gt;
&lt;p&gt;At a high level, optimizing LCP can be summarized in four steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ensure the LCP resource starts loading as early as possible.&lt;/li&gt;
&lt;li&gt;Ensure the LCP element can render as soon as its resource finishes loading.&lt;/li&gt;
&lt;li&gt;Reduce the load time of the LCP resource as much as you can without sacrificing quality.&lt;/li&gt;
&lt;li&gt;Deliver the initial HTML document as fast as possible.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you’re able to follow these steps on your pages, then you should feel confident that you’re delivering an optimal loading experience to your users, and you should see that reflected in your real-world LCP scores.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author><author>
      <name>Barry Pollard</name>
    </author>
  </entry>
  
  <entry>
    <title>Web Vitals</title>
    <link href="https://web.dev/vitals/"/>
    <updated>2020-04-30T00:00:00Z</updated>
    <id>https://web.dev/vitals/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;New:&lt;/strong&gt; &lt;a href=&quot;https://web.dev/inp/&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt; is no longer experimental! &lt;a href=&quot;https://web.dev/inp-cwv/&quot;&gt;Learn more&lt;/a&gt; about our plans to replace FID with INP as a Core Web Vital in March 2024. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Optimizing for quality of user experience is key to the long-term success of any
site on the web. Whether you&#39;re a business owner, marketer, or developer, Web
Vitals can help you quantify the experience of your site and identify
opportunities to improve.&lt;/p&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#overview&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Web Vitals is an initiative by Google to provide unified guidance for quality
signals that are essential to delivering a great user experience on the web.&lt;/p&gt;
&lt;p&gt;Google has provided a number of tools over the years to measure and report on
performance. Some developers are experts at using these tools, while others have
found the abundance of both tools and metrics challenging to keep up with.&lt;/p&gt;
&lt;p&gt;Site owners should not have to be performance experts to understand the
quality of experience they are delivering to their users. The Web Vitals
initiative aims to simplify the landscape, and help sites focus on the metrics
that matter most, the &lt;strong&gt;Core Web Vitals&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;core-web-vitals&quot;&gt;Core Web Vitals &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#core-web-vitals&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Core Web Vitals are the subset of Web Vitals that apply to all web pages, should
be measured by all site owners, and will be surfaced across all Google tools.
Each of the Core Web Vitals represents a distinct facet of the user experience,
is measurable &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#how-metrics-are-measured&quot;&gt;in the
field&lt;/a&gt;,
and reflects the real-world experience of a critical
&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#how-metrics-are-measured&quot;&gt;user-centric&lt;/a&gt;
outcome.&lt;/p&gt;
&lt;p&gt;The metrics that make up Core Web Vitals will &lt;a href=&quot;https://web.dev/vitals/#evolving-web-vitals&quot;&gt;evolve&lt;/a&gt;
over time. The current set for 2020 focuses on three aspects of the user
experience—&lt;em&gt;loading&lt;/em&gt;, &lt;em&gt;interactivity&lt;/em&gt;, and &lt;em&gt;visual stability&lt;/em&gt;—and includes the
following metrics (and their respective thresholds):&lt;/p&gt;
&lt;div class=&quot;auto-grid&quot; style=&quot;--auto-grid-min-item-size: 200px;&quot;&gt;
  &lt;img alt=&quot;Largest Contentful Paint threshold recommendations&quot; decoding=&quot;async&quot; height=&quot;350&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZZU8Z7TMKXmzZT2mCjJU.svg&quot; width=&quot;400&quot; /&gt;
  &lt;img alt=&quot;First Input Delay threshold recommendations&quot; decoding=&quot;async&quot; height=&quot;350&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iHYrrXKe4QRcb2uu8eV8.svg&quot; width=&quot;400&quot; /&gt;
  &lt;img alt=&quot;Cumulative Layout Shift threshold recommendations&quot; decoding=&quot;async&quot; height=&quot;350&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dgpDFckbHwwOKdIGDa3N.svg&quot; width=&quot;400&quot; /&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;&lt;/strong&gt;: measures &lt;em&gt;loading&lt;/em&gt; performance.
To provide a good user experience, LCP should occur within &lt;strong&gt;2.5 seconds&lt;/strong&gt; of
when the page first starts loading.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt;&lt;/strong&gt;: measures &lt;em&gt;interactivity&lt;/em&gt;. To provide a
good user experience, pages should have a FID of &lt;strong&gt;100 milliseconds&lt;/strong&gt; or less.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/cls/&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt;&lt;/strong&gt;: measures &lt;em&gt;visual stability&lt;/em&gt;. To
provide a good user experience, pages should maintain a CLS of &lt;strong&gt;0.1.&lt;/strong&gt; or
less.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For each of the above metrics, to ensure you&#39;re hitting the recommended target
for most of your users, a good threshold to measure is the &lt;strong&gt;75th percentile&lt;/strong&gt;
of page loads, segmented across mobile and desktop devices.&lt;/p&gt;
&lt;p&gt;Tools that assess Core Web Vitals compliance should consider a page passing if
it meets the recommended targets at the 75th percentile for all of the above
three metrics.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; To learn more about the research and methodology behind these recommendations, see: &lt;a href=&quot;https://web.dev/defining-core-web-vitals-thresholds/&quot;&gt;Defining the Core Web Vitals metrics thresholds&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;lifecycle&quot;&gt;Lifecycle &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#lifecycle&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Metrics on the Core Web Vitals track go through a lifecycle consisting of three phases: experimental, pending, and stable.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The three lifecycle phases of Core Web Vitals metrics, visualized as a series of three chevrons. From left to right, the phases are Experimental, Pending, and Stable.&quot; decoding=&quot;async&quot; height=&quot;31&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/jL3OLOhcWUQDnR4XjewLBx4e3PC3/gCO4TElL05PwDZJiu3r9.svg&quot; style=&quot;background-color: transparent; min-width: 100%; height: auto;&quot; width=&quot;300&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The table below reflects where all Core Web Vitals currently are in their lifecycle:&lt;/p&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;th&gt;
        Experimental
      &lt;/th&gt;
      &lt;th&gt;
        Pending
      &lt;/th&gt;
      &lt;th&gt;
        Stable
      &lt;/th&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;td&gt;
        &amp;nbsp;
      &lt;/td&gt;
      &lt;td style=&quot;vertical-align: top;&quot;&gt;
        &lt;a href=&quot;https://web.dev/inp/&quot; rel=&quot;noopener&quot;&gt;INP&lt;/a&gt;
      &lt;/td&gt;
      &lt;td style=&quot;vertical-align: top;&quot;&gt;
        &lt;a href=&quot;https://web.dev/lcp/&quot; rel=&quot;noopener&quot;&gt;LCP&lt;/a&gt;&lt;br /&gt;
        &lt;a href=&quot;https://web.dev/cls/&quot; rel=&quot;noopener&quot;&gt;CLS&lt;/a&gt;&lt;br /&gt;
        &lt;a href=&quot;https://web.dev/fid/&quot; rel=&quot;noopener&quot;&gt;FID&lt;/a&gt;
      &lt;/td&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Each phase is designed to signal to developers how they should think about each metric:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Experimental metrics&lt;/strong&gt; are prospective Core Web Vitals that may still be undergoing significant changes depending on testing and community feedback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pending metrics&lt;/strong&gt; are future Core Web Vitals that have passed the testing and feedback stage and have a well-defined timeline to becoming stable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stable metrics&lt;/strong&gt; are the current set of Core Web Vitals that Chrome considers essential for great user experiences.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;experimental&quot;&gt;Experimental &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#experimental&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When a metric is initially developed and enters the ecosystem, it is considered an &lt;em&gt;experimental metric&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The purpose of the experimental phase is to assess a metric&#39;s fitness, first by exploring the problem to be solved, and possibly iterate on what previous metrics may have failed to address. For example, &lt;a href=&quot;https://web.dev/inp/&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt; was initially developed as an experimental metric to address the runtime performance issues present on the web more comprehensively than &lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The experimental phase of Core Web Vitals lifecycle is also intended to give flexibility in a metric&#39;s development by identifying bugs and even exploring changes to its initial definition. It&#39;s also the phase in which community feedback is most important.&lt;/p&gt;
&lt;h4 id=&quot;pending&quot;&gt;Pending &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#pending&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When the Chrome team determines that an experimental metric has received sufficient feedback and proven its efficacy, it becomes a &lt;em&gt;pending metric&lt;/em&gt;. Pending metrics are held in this phase for a minimum of six months to give the ecosystem time to adapt. The only remaining hurdle for a metric to advance beyond the pending phase is to wait out the transition period. Community feedback remains an important aspect of this phase, as more developers begin to use the metric.&lt;/p&gt;
&lt;h4 id=&quot;stable&quot;&gt;Stable &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#stable&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When a Core Web Vital candidate metric is finalized, it becomes a &lt;em&gt;stable metric&lt;/em&gt;—for metrics that are on the Core Web Vitals track, this means the metric becomes a Core Web Vital.&lt;/p&gt;
&lt;p&gt;Stable metrics are actively supported, and may be subject to bug fixes and definition changes. Stable Core Web Vitals metrics won&#39;t change more than once per year. Any change to a Core Web Vital will be clearly communicated in the metric&#39;s official documentation, as well as in the metric&#39;s CHANGELOG. Core Web Vitals are also included in any assessments.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Metrics that are stable are not necessarily permanent. While stable metrics are long-lived, a stable metric can be retired and replaced by another metric if it addresses the problem area more effectively. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;tools-to-measure-and-report-core-web-vitals&quot;&gt;Tools to measure and report Core Web Vitals &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#tools-to-measure-and-report-core-web-vitals&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Google believes that the Core Web Vitals are critical to all web experiences. As
a result, it is committed to surfacing these metrics &lt;a href=&quot;https://web.dev/vitals-tools/&quot;&gt;in all of its popular
tools&lt;/a&gt;. The following sections details which tools support the
Core Web Vitals.&lt;/p&gt;
&lt;h4 id=&quot;field-tools-to-measure-core-web-vitals&quot;&gt;Field tools to measure Core Web Vitals &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#field-tools-to-measure-core-web-vitals&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience
Report&lt;/a&gt;
collects anonymized, real user measurement data for each Core Web Vital. This
data enables site owners to quickly assess their performance without requiring
them to manually instrument analytics on their pages, and powers tools like
&lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed Insights&lt;/a&gt;,
and Search Console&#39;s &lt;a href=&quot;https://support.google.com/webmasters/answer/9205520&quot; rel=&quot;noopener&quot;&gt;Core Web Vitals
report&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;td&gt;&amp;nbsp;&lt;/td&gt;
      &lt;td&gt;LCP&lt;/td&gt;
      &lt;td&gt;FID&lt;/td&gt;
      &lt;td&gt;CLS&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot;&gt;
        Chrome User Experience Report&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;✔&lt;/td&gt;
      &lt;td&gt;✔&lt;/td&gt;
      &lt;td&gt;✔&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://developers.google.com/speed/pagespeed/insights/&quot;&gt;
        PageSpeed Insights&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;✔&lt;/td&gt;
      &lt;td&gt;✔&lt;/td&gt;
      &lt;td&gt;✔&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://support.google.com/webmasters/answer/9205520&quot;&gt;
        Search Console (Core Web Vitals report)&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;✔&lt;/td&gt;
      &lt;td&gt;✔&lt;/td&gt;
      &lt;td&gt;✔&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; For guidance on how to use these tools, and which tool is right for your use case, see: &lt;a href=&quot;https://web.dev/vitals-measurement-getting-started/&quot;&gt;Getting started with measuring Web Vitals&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;The data provided by Chrome User Experience Report offers a quick way to assess
the performance of sites, but it does not provide the detailed, per-pageview
telemetry that is often necessary to accurately diagnose, monitor, and quickly
react to regressions. As a result, we strongly recommend that sites set up their
own real-user monitoring.&lt;/p&gt;
&lt;h4 id=&quot;measure-core-web-vitals-in-javascript&quot;&gt;Measure Core Web Vitals in JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#measure-core-web-vitals-in-javascript&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Each of the Core Web Vitals can be measured in JavaScript using standard web
APIs.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Note that the Core Web Vitals measured in JavaScript using public APIs may differ from the Core Web Vitals reported by CrUX. Read &lt;a href=&quot;https://web.dev/crux-and-rum-differences/&quot;&gt;this article&lt;/a&gt; for more info. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;The easiest way to measure all the Core Web Vitals is to use the
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;web-vitals&lt;/a&gt; JavaScript library, a
small, production-ready wrapper around the underlying web APIs that measures
each metric in a way that accurately matches how they&#39;re reported by all the
Google tools listed above.&lt;/p&gt;
&lt;p&gt;With the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;web-vitals&lt;/a&gt; library,
measuring each metric is as simple as calling a single function (see the
documentation for complete
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#usage&quot; rel=&quot;noopener&quot;&gt;usage&lt;/a&gt; and
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#api&quot; rel=&quot;noopener&quot;&gt;API&lt;/a&gt; details):&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onCLS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onFID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onLCP&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;web-vitals&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendToAnalytics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;metric&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metric&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sendBeacon &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendBeacon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/analytics&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/analytics&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;POST&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;keepalive&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onCLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onFID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onLCP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sendToAnalytics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Once you&#39;ve configured your site to use the
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;web-vitals&lt;/a&gt; library to measure and
send your Core Web Vitals data to an analytics endpoint, the next step is to
aggregate and report on that data to see if your pages are meeting the
recommended thresholds for at least 75% of page visits.&lt;/p&gt;
&lt;p&gt;While some analytics providers have built-in support for Core Web Vitals
metrics, even those that don&#39;t should include basic custom metric features that
allow you to measure Core Web Vitals in their tool.&lt;/p&gt;
&lt;p&gt;One example of this is the &lt;a href=&quot;https://github.com/GoogleChromeLabs/web-vitals-report&quot; rel=&quot;noopener&quot;&gt;Web Vitals
Report&lt;/a&gt;, which allows
site owners to measure their Core Web Vitals using Google Analytics. For
guidance on measuring Core Web Vitals using other analytics tools, see &lt;a href=&quot;https://web.dev/vitals-field-measurement-best-practices/&quot;&gt;Best
practices for measuring Web Vitals in the
field&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also report on each of the Core Web Vitals without writing any code
using the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals-extension&quot; rel=&quot;noopener&quot;&gt;Web Vitals Chrome
Extension&lt;/a&gt;. This extension
uses the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;web-vitals&lt;/a&gt; library to
measure each of these metrics and display them to users as they browse the web.&lt;/p&gt;
&lt;p&gt;This extension can be helpful in understanding the performance of your own
sites, your competitor&#39;s sites, and the web at large.&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;&amp;nbsp;&lt;/th&gt;
        &lt;th&gt;LCP&lt;/th&gt;
        &lt;th&gt;FID&lt;/th&gt;
        &lt;th&gt;CLS&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot;&gt;web-vitals&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;✔&lt;/td&gt;
        &lt;td&gt;✔&lt;/td&gt;
        &lt;td&gt;✔&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals-extension&quot;&gt;
          Web Vitals Extension&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;✔&lt;/td&gt;
        &lt;td&gt;✔&lt;/td&gt;
        &lt;td&gt;✔&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Alternatively, developers who prefer to measure these metrics directly via the
underlying web APIs can refer to these metric guides for implementation details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/lcp/#measure-lcp-in-javascript&quot;&gt;Measure LCP in JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/fid/#measure-fid-in-javascript&quot;&gt;Measure FID in JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/cls/#measure-cls-in-javascript&quot;&gt;Measure CLS in JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; For additional guidance on how to measure these metrics using popular analytics services (or your own in-house analytics tools), see: &lt;a href=&quot;https://web.dev/vitals-field-measurement-best-practices/&quot;&gt;Best practices for measuring Web Vitals in the field&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;lab-tools-to-measure-core-web-vitals&quot;&gt;Lab tools to measure Core Web Vitals &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#lab-tools-to-measure-core-web-vitals&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;While all of the Core Web Vitals are, first and foremost, field metrics, many of
them are also measurable in the lab.&lt;/p&gt;
&lt;p&gt;Lab measurement is the best way to test the performance of features during
development—before they&#39;ve been released to users. It&#39;s also the best way to
catch performance regressions before they happen.&lt;/p&gt;
&lt;p&gt;The following tools can be used to measure the Core Web Vitals in a lab
environment:&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;&amp;nbsp;&lt;/th&gt;
        &lt;th&gt;LCP&lt;/th&gt;
        &lt;th&gt;FID&lt;/th&gt;
        &lt;th&gt;CLS&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/&quot;&gt;
          Chrome DevTools&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;✔&lt;/td&gt;
        &lt;td&gt;✘ (use &lt;a href=&quot;https://web.dev/tbt/&quot;&gt;TBT&lt;/a&gt; instead)&lt;/td&gt;
        &lt;td&gt;✔&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot;&gt;
          Lighthouse&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;✔&lt;/td&gt;
        &lt;td&gt;✘ (use &lt;a href=&quot;https://web.dev/tbt/&quot;&gt;TBT&lt;/a&gt; instead)&lt;/td&gt;
        &lt;td&gt;✔&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Tools like Lighthouse that load pages in a simulated environment without a user cannot measure FID (there is no user input). However, the Total Blocking Time (TBT) metric is lab-measurable and is an excellent proxy for FID. Performance optimizations that improve TBT in the lab should improve FID in the field (see performance recommendations below). &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;While lab measurement is an essential part of delivering great experiences, it
is not a substitute for field measurement.&lt;/p&gt;
&lt;p&gt;The performance of a site can vary dramatically based on a user&#39;s device
capabilities, their network conditions, what other processes may be running on
the device, and how they&#39;re interacting with the page. In fact, each of the Core
Web Vitals metrics can have its score affected by user interaction. Only field
measurement can accurately capture the complete picture.&lt;/p&gt;
&lt;h3 id=&quot;recommendations-for-improving-your-scores&quot;&gt;Recommendations for improving your scores &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#recommendations-for-improving-your-scores&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once you&#39;ve measured the Core Web Vitals and identified areas for improvement,
the next step is to optimize. The following guides offer specific
recommendations for how to optimize your pages for each of the Core Web Vitals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/optimize-lcp/&quot;&gt;Optimize LCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/optimize-fid/&quot;&gt;Optimize FID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/optimize-cls/&quot;&gt;Optimize CLS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;other-web-vitals&quot;&gt;Other Web Vitals &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#other-web-vitals&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While the Core Web Vitals are the critical metrics for understanding and
delivering a great user experience, there are other vital metrics as well.&lt;/p&gt;
&lt;p&gt;These other Web Vitals often serve as proxy or supplemental metrics for the Core
Web Vitals, to help capture a larger part of the experience or to aid in
diagnosing a specific issue.&lt;/p&gt;
&lt;p&gt;For example, the metrics &lt;a href=&quot;https://web.dev/ttfb/&quot;&gt;Time to First Byte (TTFB)&lt;/a&gt; and
&lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt; are both vital aspects of the &lt;em&gt;loading&lt;/em&gt;
experience, and are both useful in diagnosing issues with LCP (slow &lt;a href=&quot;https://web.dev/overloaded-server/&quot;&gt;server
response times&lt;/a&gt; or &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources/&quot; rel=&quot;noopener&quot;&gt;render-blocking
resources&lt;/a&gt;, respectively).&lt;/p&gt;
&lt;p&gt;Similarly, metrics like &lt;a href=&quot;https://web.dev/tbt/&quot;&gt;Total Blocking Time (TBT)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/tti/&quot;&gt;Time to
Interactive (TTI)&lt;/a&gt; are lab metrics that are vital in catching and
diagnosing potential &lt;em&gt;interactivity&lt;/em&gt; issues that will impact FID. However, they
are not part of the Core Web Vitals set because they are not field-measurable,
nor do they reflect a
&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#how-metrics-are-measured&quot;&gt;user-centric&lt;/a&gt;
outcome.&lt;/p&gt;
&lt;h2 id=&quot;evolving-web-vitals&quot;&gt;Evolving Web Vitals &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/vitals/#evolving-web-vitals&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Web Vitals and Core Web Vitals represent the best available signals developers
have today to measure quality of experience across the web, but these signals
are not perfect and future improvements or additions should be expected.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Core Web Vitals&lt;/strong&gt; are relevant to all web pages and featured across
relevant Google tools. Changes to these metrics will have wide-reaching impact;
as such, developers should expect the definitions and thresholds of the Core Web
Vitals to be stable, and updates to have prior notice and a predictable, annual
cadence.&lt;/p&gt;
&lt;p&gt;The other Web Vitals are often context or tool specific, and may be more
experimental than the Core Web Vitals. As such, their definitions and thresholds
may change with greater frequency.&lt;/p&gt;
&lt;p&gt;For all Web Vitals, changes will be clearly documented in this public
&lt;a href=&quot;http://bit.ly/chrome-speed-metrics-changelog&quot; rel=&quot;noopener&quot;&gt;CHANGELOG&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>User-centric performance metrics</title>
    <link href="https://web.dev/user-centric-performance-metrics/"/>
    <updated>2019-11-08T00:00:00Z</updated>
    <id>https://web.dev/user-centric-performance-metrics/</id>
    <content type="html" mode="escaped">&lt;p&gt;We&#39;ve all heard how important performance is. But when we talk about
performance—and about making websites &amp;quot;fast&amp;quot;—what specifically do we
mean?&lt;/p&gt;
&lt;p&gt;The truth is performance is relative:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A site might be fast for one user (on a fast network with a powerful device)
but slow for another user (on a slow network with a low-end device).&lt;/li&gt;
&lt;li&gt;Two sites may finish loading in the exact same amount of time, yet one may
&lt;em&gt;seem&lt;/em&gt; to load faster (if it loads content progressively rather than waiting
until the end to display anything).&lt;/li&gt;
&lt;li&gt;A site might &lt;em&gt;appear&lt;/em&gt; to load quickly but then respond slowly (or not at all)
to user interaction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So when talking about performance, it&#39;s important to be precise and to refer to
performance in terms of objective criteria that can be quantitatively measured.
These criteria are known as &lt;em&gt;metrics&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;But just because a metric is based on objective criteria and can be
quantitatively measured, it doesn&#39;t necessarily mean those measurements are
useful.&lt;/p&gt;
&lt;h2 id=&quot;defining-metrics&quot;&gt;Defining metrics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-centric-performance-metrics/#defining-metrics&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Historically, web performance has been measured with the
&lt;code&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/load_event&quot; rel=&quot;noopener&quot;&gt;load&lt;/a&gt;&lt;/code&gt;
event. However, even though &lt;code&gt;load&lt;/code&gt; is a well-defined moment in a
page&#39;s lifecycle, that moment doesn&#39;t necessarily correspond with anything the
user cares about.&lt;/p&gt;
&lt;p&gt;For example, a server could respond with a minimal page that &amp;quot;loads&amp;quot; immediately
but then defers fetching content and displaying anything on the page until
several seconds after the &lt;code&gt;load&lt;/code&gt; event fires. While such a page might
technically have a fast load time, that time would not correspond to how a user
actually experiences the page loading.&lt;/p&gt;
&lt;p&gt;Over the past few years, members of the Chrome team—in collaboration with
the &lt;a href=&quot;https://www.w3.org/webperf/&quot; rel=&quot;noopener&quot;&gt;W3C Web Performance Working Group&lt;/a&gt;—have
been working to standardize a set of new APIs and metrics that more accurately
measure how users experience the performance of a web page.&lt;/p&gt;
&lt;p&gt;To help ensure the metrics are relevant to users, we frame them around a few key
questions:&lt;/p&gt;
&lt;table id=&quot;questions&quot;&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;strong&gt;Is it happening?&lt;/strong&gt;&lt;/td&gt;
    &lt;td&gt;Did the navigation start successfully? Has the server responded?&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;strong&gt;Is it useful?&lt;/strong&gt;&lt;/td&gt;
    &lt;td&gt;Has enough content rendered that users can engage with it?&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;strong&gt;Is it usable?&lt;/strong&gt;&lt;/td&gt;
    &lt;td&gt;Can users interact with the page, or is it busy?&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;&lt;strong&gt;Is it delightful?&lt;/strong&gt;&lt;/td&gt;
    &lt;td&gt;Are the interactions smooth and natural, free of lag and jank?&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;h2 id=&quot;how-metrics-are-measured&quot;&gt;How metrics are measured &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-centric-performance-metrics/#how-metrics-are-measured&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Performance metrics are generally measured in one of two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;In the lab:&lt;/strong&gt; using tools to simulate a page load in a consistent,
controlled environment&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In the field&lt;/strong&gt;: on real users actually loading and interacting with the page&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Neither of these options is necessarily better or worse than the other—in
fact you generally want to use both to ensure good performance.&lt;/p&gt;
&lt;h3 id=&quot;in-the-lab&quot;&gt;In the lab &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Testing performance in the lab is essential when developing new features. Before
features are released in production, it&#39;s impossible to measure their
performance characteristics on real users, so testing them in the lab before the
feature is released is the best way to prevent performance regressions.&lt;/p&gt;
&lt;h3 id=&quot;in-the-field&quot;&gt;In the field &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;On the other hand, while testing in the lab is a reasonable proxy for
performance, it isn&#39;t necessarily reflective of how all users experience your
site in the wild.&lt;/p&gt;
&lt;p&gt;The performance of a site can vary dramatically based on a user&#39;s device
capabilities and their network conditions. It can also vary based on whether (or
how) a user is interacting with the page.&lt;/p&gt;
&lt;p&gt;Moreover, page loads may not be deterministic. For example, sites that load
personalized content or ads may experience vastly different performance
characteristics from user to user. A lab test will not capture those
differences.&lt;/p&gt;
&lt;p&gt;The only way to truly know how your site performs for your users is to actually
measure its performance as those users are loading and interacting with it. This
type of measurement is commonly referred to as &lt;a href=&quot;https://en.wikipedia.org/wiki/Real_user_monitoring&quot; rel=&quot;noopener&quot;&gt;Real User
Monitoring&lt;/a&gt;—or RUM for
short.&lt;/p&gt;
&lt;h2 id=&quot;types-of-metrics&quot;&gt;Types of metrics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-centric-performance-metrics/#types-of-metrics&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are several other types of metrics that are relevant to how users perceive
performance.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Perceived load speed:&lt;/strong&gt; how quickly a page can load and render all of its
visual elements to the screen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Load responsiveness:&lt;/strong&gt; how quickly a page can load and execute any required
JavaScript code in order for components to respond quickly to user interaction&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime responsiveness:&lt;/strong&gt; after page load, how quickly can the page respond
to user interaction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visual stability:&lt;/strong&gt; do elements on the page shift in ways that users don&#39;t
expect and potentially interfere with their interactions?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Smoothness:&lt;/strong&gt; do transitions and animations render at a consistent frame
rate and flow fluidly from one state to the next?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given all the above types of performance metrics, it&#39;s hopefully clear that no
single metric is sufficient to capture all the performance characteristics of a
page.&lt;/p&gt;
&lt;h2 id=&quot;important-metrics-to-measure&quot;&gt;Important metrics to measure &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-centric-performance-metrics/#important-metrics-to-measure&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt;:&lt;/strong&gt; measures the time from when the
page starts loading to when any part of the page&#39;s content is rendered on the
screen. &lt;em&gt;(&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;lab&lt;/a&gt;, &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;field&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;:&lt;/strong&gt; measures the time from when the
page starts loading to when the largest text block or image element is
rendered on the screen. &lt;em&gt;(&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;lab&lt;/a&gt;, &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;field&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt;:&lt;/strong&gt; measures the time from when a user first
interacts with your site (when they click a link, tap a button, or use a
custom, JavaScript-powered control) to the time when the browser is actually
able to respond to that interaction. &lt;em&gt;(&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;field&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/inp/&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt;&lt;/strong&gt;: measures the latency of every
tap, click, or keyboard interaction made with the page, and—based on the
number of interactions—selects the worst interaction latency of the page
(or close to the highest) as a single, representative value to describe a
page&#39;s overall responsiveness. &lt;em&gt;(&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;lab&lt;/a&gt;, &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;field&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/tti/&quot;&gt;Time to Interactive (TTI)&lt;/a&gt;:&lt;/strong&gt; measures the time from when the page
starts loading to when it&#39;s visually rendered, its initial scripts (if any)
have loaded, and it&#39;s capable of reliably responding to user input quickly.
&lt;em&gt;(&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;lab&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/tbt/&quot;&gt;Total Blocking Time (TBT)&lt;/a&gt;:&lt;/strong&gt; measures the total amount of time
between FCP and TTI where the main thread was blocked for long enough to
prevent input responsiveness. &lt;em&gt;(&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;lab&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/cls/&quot;&gt;Cumulative Layout Shift (CLS)&lt;/a&gt;:&lt;/strong&gt; measures the cumulative score of
all unexpected layout shifts that occur between when the page starts loading
and when its &lt;a href=&quot;https://developer.chrome.com/blog/page-lifecycle-api/&quot; rel=&quot;noopener&quot;&gt;lifecycle
state&lt;/a&gt;
changes to hidden. &lt;em&gt;(&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;lab&lt;/a&gt;, &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;field&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://web.dev/ttfb/&quot;&gt;Time to First Byte (TTFB)&lt;/a&gt;&lt;/strong&gt;: measures the time it takes for the
network to respond to a user request with the first byte of a resource.
&lt;em&gt;(&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;lab&lt;/a&gt;, &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;field&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While this list includes metrics measuring many of the various aspects of
performance relevant to users, it does not include everything. For example, runtime
responsiveness and smoothness are not currently covered.&lt;/p&gt;
&lt;p&gt;In some cases, new metrics will be introduced to cover missing areas, but in
other cases the best metrics are ones specifically tailored to your site.&lt;/p&gt;
&lt;h2 id=&quot;custom-metrics&quot;&gt;Custom metrics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-centric-performance-metrics/#custom-metrics&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The performance metrics listed above are good for getting a general
understanding of the performance characteristics of most sites on the web.
They&#39;re also good for having a common set of metrics for sites to compare their
performance against their competitors.&lt;/p&gt;
&lt;p&gt;However, there may be times when a specific site is unique in some way that
requires additional metrics to capture the full performance picture. For
example, the LCP metric is intended to measure when a page&#39;s main content has
finished loading, but there could be cases where the largest element is not part
of the page&#39;s main content and thus LCP may not be relevant.&lt;/p&gt;
&lt;p&gt;To address such cases, the Web Performance Working Group has also standardized
lower-level APIs that can be useful for implementing your own custom metrics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/user-timing/&quot; rel=&quot;noopener&quot;&gt;User Timing API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/longtasks/&quot; rel=&quot;noopener&quot;&gt;Long Tasks API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/element-timing/&quot; rel=&quot;noopener&quot;&gt;Element Timing API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/navigation-timing/&quot; rel=&quot;noopener&quot;&gt;Navigation Timing API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/resource-timing/&quot; rel=&quot;noopener&quot;&gt;Resource Timing API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/server-timing/&quot; rel=&quot;noopener&quot;&gt;Server timing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Refer to the guide on &lt;a href=&quot;https://web.dev/custom-metrics/&quot;&gt;Custom Metrics&lt;/a&gt; to learn how to use
these APIs to measure performance characteristics specific to your site.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>Custom metrics</title>
    <link href="https://web.dev/custom-metrics/"/>
    <updated>2019-11-08T00:00:00Z</updated>
    <id>https://web.dev/custom-metrics/</id>
    <content type="html" mode="escaped">&lt;p&gt;There&#39;s a lot of value in having &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/&quot;&gt;user-centric metrics&lt;/a&gt;
that you can measure, universally, on any given website. These metrics allow you
to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand how real users experience the web as a whole&lt;/li&gt;
&lt;li&gt;Easily compare your site to a competitor&#39;s&lt;/li&gt;
&lt;li&gt;Track useful and actionable data in your analytics tools without needing to
write custom code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Universal metrics offer a good baseline, but in many cases you need to measure
&lt;em&gt;more&lt;/em&gt; than just these metrics in order to capture the full experience for your
particular site.&lt;/p&gt;
&lt;p&gt;Custom metrics allow you to measure aspects of your site&#39;s experience that may
only apply to your site, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How long it takes for a single page app (SPA) to transition from one &amp;quot;page&amp;quot; to
another&lt;/li&gt;
&lt;li&gt;How long it takes for a page to display data fetched from a database for
logged-in users&lt;/li&gt;
&lt;li&gt;How long it takes for a server-side-rendered (SSR) app to
&lt;a href=&quot;https://addyosmani.com/blog/rehydration/&quot; rel=&quot;noopener&quot;&gt;hydrate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The cache hit rate for resources loaded by returning visitors&lt;/li&gt;
&lt;li&gt;The event latency of click or keyboard events in a game&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;apis-to-measure-custom-metrics&quot;&gt;APIs to measure custom metrics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#apis-to-measure-custom-metrics&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Historically web developers haven&#39;t had many low-level APIs to measure
performance, and as a result they&#39;ve had to resort to hacks in order to measure
whether a site was performing well.&lt;/p&gt;
&lt;p&gt;For example, it&#39;s possible to determine whether the main thread is blocked due
to long-running JavaScript tasks by running a &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop and
calculating the delta between each frame. If the delta is significantly longer
than the display&#39;s framerate, you can report that as a long task. Such hacks are
not recommended, though, because they actually affect performance themselves
(by draining battery, for example).&lt;/p&gt;
&lt;p&gt;The first rule of effective performance measurement is to make sure your
performance measurement techniques aren&#39;t causing performance issues themselves.
So for any custom metrics you measure on your site, it&#39;s best to use one of the
following APIs if possible.&lt;/p&gt;
&lt;h3 id=&quot;performance-observer&quot;&gt;Performance Observer &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#performance-observer&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 52, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      52
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 57, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      57
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      79
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 11, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      11
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;Understanding the PerformanceObserver API is critical to creating custom
performance metrics because it&#39;s the mechanism by which you get data from all
other performance APIs discussed in this article.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;PerformanceObserver&lt;/code&gt; you can subscribe passively to performance-related
events, which means these APIs generally will not interfere with the performance
of the page, as their callbacks are generally fired during &lt;a href=&quot;https://w3c.github.io/requestidlecallback/#idle-periods&quot; rel=&quot;noopener&quot;&gt;idle
periods&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You create a &lt;code&gt;PerformanceObserver&lt;/code&gt; by passing it a callback to be run whenever
new performance entries are dispatched. Then you tell the observer what types of
entries to listen for via the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver/observe&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;observe()&lt;/code&gt;&lt;/a&gt;
method:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Catch errors since some browsers throw when using the new `type` option.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// https://bugs.webkit.org/show_bug.cgi?id=209216&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; po &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Log the entry and all associated details.&lt;/span&gt;&lt;br /&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toJSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  po&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;some-entry-type&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do nothing if the browser doesn&#39;t support this API.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The sections below list all the various entry types available for observing, but
in newer browsers you can inspect what entry types are available via the static
&lt;a href=&quot;https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;PerformanceObserver.supportedEntryTypes&lt;/code&gt;&lt;/a&gt;
property.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The object passed to the &lt;code&gt;observe()&lt;/code&gt; method can also specify an &lt;code&gt;entryTypes&lt;/code&gt; array (in order to observe more than one entry type via the same observer). While specifying &lt;code&gt;entryTypes&lt;/code&gt; is an older option with wider browser support, using &lt;code&gt;type&lt;/code&gt; is now preferred, as it allows for specifying additional entry-specific observation configuration (such as the &lt;code&gt;buffered&lt;/code&gt; flag, discussed next). &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;observing-entries-that-already-happened&quot;&gt;Observing entries that already happened &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#observing-entries-that-already-happened&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;By default, &lt;code&gt;PerformanceObserver&lt;/code&gt; objects can only observe entries as they
occur. This can be problematic if you want to load your performance analytics
code lazily (to not block higher-priority resources).&lt;/p&gt;
&lt;p&gt;To get historical entries (after they&#39;ve occurred), set the &lt;code&gt;buffered&lt;/code&gt; flag to
&lt;code&gt;true&lt;/code&gt; when you call &lt;code&gt;observe()&lt;/code&gt;. The browser will include historical entries
from its &lt;a href=&quot;https://w3c.github.io/performance-timeline/#dfn-performance-entry-buffer&quot; rel=&quot;noopener&quot;&gt;performance entry
buffer&lt;/a&gt;
the first time that your &lt;code&gt;PerformanceObserver&lt;/code&gt; callback is called.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;po&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;some-entry-type&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; To avoid memory issues, the performance entry buffer is not unlimited. For most typical page loads it&#39;s unlikely that the buffer will fill up and entries will be missed. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;legacy-performance-apis-to-avoid&quot;&gt;Legacy performance APIs to avoid &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#legacy-performance-apis-to-avoid&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Prior to the Performance Observer API, developers could access performance
entries using the following three methods defined on the
&lt;a href=&quot;https://w3c.github.io/performance-timeline/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;performance&lt;/code&gt;&lt;/a&gt; object:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Performance/getEntries&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getEntries()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Performance/getEntriesByName&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getEntriesByName()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Performance/getEntriesByType&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getEntriesByType()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While these APIs are still supported, their usage is not recommended because
they don&#39;t allow you to listen for when new entries are emitted. In addition,
many new APIs (such as Long Tasks) are not exposed via the &lt;code&gt;performance&lt;/code&gt; object,
they&#39;re only exposed via &lt;code&gt;PerformanceObserver&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Unless you specifically need Internet Explorer compatibility, it&#39;s best to avoid
these methods in your code and use &lt;code&gt;PerformanceObserver&lt;/code&gt; going forward.&lt;/p&gt;
&lt;h3 id=&quot;user-timing-api&quot;&gt;User Timing API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#user-timing-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://w3c.github.io/user-timing/&quot; rel=&quot;noopener&quot;&gt;User Timing API&lt;/a&gt; is your general
purpose measurement API for time-based metrics. It allows you to arbitrarily
mark points in time and then later measure the duration between those marks.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Record the time immediately before running a task.&lt;/span&gt;&lt;br /&gt;performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mark&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;myTask:start&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doMyTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Record the time immediately after running a task.&lt;/span&gt;&lt;br /&gt;performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mark&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;myTask:end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Measure the delta between the start and end of the task&lt;/span&gt;&lt;br /&gt;performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;measure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;myTask&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;myTask:start&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;myTask:end&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;While APIs like &lt;code&gt;Date.now()&lt;/code&gt; or &lt;code&gt;performance.now()&lt;/code&gt; give you similar abilities,
the benefit of using the User Timing API is it integrates well with performance
tooling. For example, Chrome DevTools visualizes &lt;a href=&quot;https://developer.chrome.com/blog/new-in-devtools-67/#tabs&quot; rel=&quot;noopener&quot;&gt;User Timing measurements in the
Performance panel&lt;/a&gt;, and many analytics providers will also automatically track
any measurements you make and send the duration data to their analytics back
end.&lt;/p&gt;
&lt;p&gt;To report User Timing measurements, you can use
&lt;a href=&quot;https://web.dev/custom-metrics/#performance-observer&quot;&gt;PerformanceObserver&lt;/a&gt; and register to observe entries of
type &lt;code&gt;measure&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Catch errors since some browsers throw when using the new `type` option.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// https://bugs.webkit.org/show_bug.cgi?id=209216&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Create the performance observer.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; po &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Log the entry and all associated details.&lt;/span&gt;&lt;br /&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toJSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Start listening for `measure` entries to be dispatched.&lt;/span&gt;&lt;br /&gt;  po&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;measure&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do nothing if the browser doesn&#39;t support this API.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;long-tasks-api&quot;&gt;Long Tasks API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#long-tasks-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 58, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      58
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
79
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceLongTaskTiming#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://w3c.github.io/longtasks/&quot; rel=&quot;noopener&quot;&gt;Long Tasks API&lt;/a&gt; is useful for knowing
when the browser&#39;s main thread is blocked for long enough to affect frame rate
or input latency. Currently the API will report any tasks that execute for
longer than 50 milliseconds (ms).&lt;/p&gt;
&lt;p&gt;Anytime you need to run expensive code (or load and execute large scripts) it&#39;s
useful to track whether or not that code blocked the main thread. In fact, many
higher-level metrics are built on top of the Long Tasks API themselves (such as
&lt;a href=&quot;https://web.dev/tti/&quot;&gt;Time to Interactive (TTI)&lt;/a&gt; and &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/&quot; rel=&quot;noopener&quot;&gt;Total Blocking Time
(TBT)&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;To determine when long tasks happen, you can use
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver&quot; rel=&quot;noopener&quot;&gt;PerformanceObserver&lt;/a&gt;
and register to observe entries of type &lt;code&gt;longtask&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Catch errors since some browsers throw when using the new `type` option.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// https://bugs.webkit.org/show_bug.cgi?id=209216&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Create the performance observer.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; po &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Log the entry and all associated details.&lt;/span&gt;&lt;br /&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toJSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Start listening for `longtask` entries to be dispatched.&lt;/span&gt;&lt;br /&gt;  po&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;longtask&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do nothing if the browser doesn&#39;t support this API.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;element-timing-api&quot;&gt;Element Timing API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#element-timing-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 77, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      77
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
79
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceElementTiming#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt; metric is
useful for knowing when the largest image or text block was painted to the
screen, but in some cases you want to measure the render time of a different
element.&lt;/p&gt;
&lt;p&gt;For these cases, you can use the &lt;a href=&quot;https://wicg.github.io/element-timing/&quot; rel=&quot;noopener&quot;&gt;Element Timing
API&lt;/a&gt;. In fact, the Largest Contentful
Paint API is actually built on top of the Element Timing API and adds automatic
reporting of the largest contentful element, but you can report on additional
elements by explicitly adding the &lt;code&gt;elementtiming&lt;/code&gt; attribute to them, and
registering a PerformanceObserver to observe the element entry type.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;elementtiming&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;hero-image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;elementtiming&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;important-paragraph&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;This is text I care about.&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;...&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Catch errors since some browsers throw when using the new `type` option.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// https://bugs.webkit.org/show_bug.cgi?id=209216&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Create the performance observer.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; po &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entryList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Log the entry and all associated details.&lt;/span&gt;&lt;br /&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toJSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Start listening for `element` entries to be dispatched.&lt;/span&gt;&lt;br /&gt;  po&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;element&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do nothing if the browser doesn&#39;t support this API.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The types of elements considered for Largest Contentful Paint are the same as those observable via the Element Timing API. If you add the &lt;code&gt;elementtiming&lt;/code&gt; attribute to an element that isn&#39;t one of those types, the attribute will be ignored. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;event-timing-api&quot;&gt;Event Timing API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#event-timing-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt; metric measures the time from when a user
first interacts with a page to the time when the browser is actually able to
begin processing event handlers in response to that interaction. However, in
some cases it may also be useful to measure the event processing time itself as
well as the time until the next frame can be rendered.&lt;/p&gt;
&lt;p&gt;This is possible with the &lt;a href=&quot;https://wicg.github.io/event-timing/&quot; rel=&quot;noopener&quot;&gt;Event Timing
API&lt;/a&gt; (which is used to measure FID) as it
exposes a number of timestamps in the event lifecycle, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/performance-timeline/#dom-performanceentry-starttime&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;startTime&lt;/code&gt;&lt;/a&gt;:
the time when the browser receives the event.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/event-timing/#dom-performanceeventtiming-processingstart&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;processingStart&lt;/code&gt;&lt;/a&gt;:
the time when the browser is able to begin processing event handlers for
the event.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/event-timing/#dom-performanceeventtiming-processingend&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;processingEnd&lt;/code&gt;&lt;/a&gt;:
time when the browser finishes executing all synchronous code initiated from
event handlers for this event.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/event-timing/#dom-performanceeventtiming-processingstart&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;duration&lt;/code&gt;&lt;/a&gt;:
the time (rounded to 8ms for security reasons) between when the browser
receives the event until it&#39;s able to paint the next frame after finishing
executing all synchronous code initiated from the event handlers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following example shows how to use these these values to create custom
measurements:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Catch errors since some browsers throw when using the new `type` option.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// https://bugs.webkit.org/show_bug.cgi?id=209216&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; po &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstInput &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entryList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Measure First Input Delay (FID).&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstInputDelay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; firstInput&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processingStart &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; firstInput&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Measure the time it takes to run all event handlers&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Note: this does not include work scheduled asynchronously using&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// methods like `requestAnimationFrame()` or `setTimeout()`.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstInputProcessingTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; firstInput&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processingEnd &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; firstInput&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processingStart&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Measure the entire duration of the event, from when input is received by&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// the browser until the next frame can be painted after processing all&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// event handlers.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Note: similar to above, this value does not include work scheduled&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// asynchronously using `requestAnimationFrame()` or `setTimeout()`.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// And for security reasons, this value is rounded to the nearest 8ms.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; firstInputDuration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; firstInput&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;duration&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Log these values the console.&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      firstInputDelay&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      firstInputProcessingTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      firstInputDuration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  po&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;first-input&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do nothing if the browser doesn&#39;t support this API.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;resource-timing-api&quot;&gt;Resource Timing API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#resource-timing-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://w3c.github.io/resource-timing/&quot; rel=&quot;noopener&quot;&gt;Resource Timing API&lt;/a&gt; gives
developers detailed insight into how resources for a particular page were
loaded. Despite the name of the API, the information it provides is not just
limited to timing data (though there&#39;s &lt;a href=&quot;https://w3c.github.io/resource-timing/#processing-model&quot; rel=&quot;noopener&quot;&gt;plenty of
that&lt;/a&gt;). Other data you
can access includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-initiatortype&quot; rel=&quot;noopener&quot;&gt;initiatorType&lt;/a&gt;:
how the resource was fetched: such as from a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag, or
from &lt;code&gt;fetch()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-nexthopprotocol&quot; rel=&quot;noopener&quot;&gt;nextHopProtocol&lt;/a&gt;:
the protocol used to fetch the resource, such as &lt;code&gt;h2&lt;/code&gt; or &lt;code&gt;quic&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-encodedbodysize&quot; rel=&quot;noopener&quot;&gt;encodedBodySize&lt;/a&gt;/&lt;a href=&quot;https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-decodedbodysize&quot; rel=&quot;noopener&quot;&gt;decodedBodySize&lt;/a&gt;]:
the size of the resource in its encoded or decoded form (respectively)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-transfersize&quot; rel=&quot;noopener&quot;&gt;transferSize&lt;/a&gt;:
the size of the resource that was actually transferred over the network. When
resources are fulfilled via the cache, this value can be much smaller than the
&lt;code&gt;encodedBodySize&lt;/code&gt;, and in some cases it can be zero (if no cache revalidation
is required).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note, you can use the &lt;code&gt;transferSize&lt;/code&gt; property of resource timing entries to
measure a &lt;em&gt;cache hit rate&lt;/em&gt; metric or perhaps even a &lt;em&gt;total cached resource size&lt;/em&gt;
metric, which may be useful in understanding how your resource caching strategy
affects performance for repeat visitors.&lt;/p&gt;
&lt;p&gt;The following example logs all resources requested by the page and indicates
whether or not each resource was fulfilled via the cache.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Catch errors since some browsers throw when using the new `type` option.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// https://bugs.webkit.org/show_bug.cgi?id=209216&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Create the performance observer.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; po &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// If transferSize is 0, the resource was fulfilled via the cache.&lt;/span&gt;&lt;br /&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transferSize &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Start listening for `resource` entries to be dispatched.&lt;/span&gt;&lt;br /&gt;  po&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;resource&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do nothing if the browser doesn&#39;t support this API.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;navigation-timing-api&quot;&gt;Navigation Timing API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#navigation-timing-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 57, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      57
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 58, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      58
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 12, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 15, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      15
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceNavigationTiming#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;a href=&quot;https://w3c.github.io/navigation-timing/&quot; rel=&quot;noopener&quot;&gt;Navigation Timing API&lt;/a&gt; is similar
to the Resource Timing API, but it reports only &lt;a href=&quot;https://developer.chrome.com/docs/workbox/caching-strategies-overview&quot; rel=&quot;noopener&quot;&gt;navigation
requests&lt;/a&gt;.
The &lt;code&gt;navigation&lt;/code&gt; entry type is also similar to the &lt;code&gt;resource&lt;/code&gt; entry type, but it
contains some &lt;a href=&quot;https://w3c.github.io/navigation-timing/#sec-PerformanceNavigationTiming&quot; rel=&quot;noopener&quot;&gt;additional
information&lt;/a&gt;
specific to only navigation requests (such as when the &lt;code&gt;DOMContentLoaded&lt;/code&gt; and
&lt;code&gt;load&lt;/code&gt; events fire).&lt;/p&gt;
&lt;p&gt;One metric many developers track to understand server response time (&lt;a href=&quot;https://en.wikipedia.org/wiki/Time_to_first_byte&quot; rel=&quot;noopener&quot;&gt;Time to
First Byte&lt;/a&gt;) is available via
the Navigation Timing API—specifically it&#39;s entry&#39;s &lt;code&gt;responseStart&lt;/code&gt; timestamp.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Catch errors since some browsers throw when using the new `type` option.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// https://bugs.webkit.org/show_bug.cgi?id=209216&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Create the performance observer.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; po &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// If transferSize is 0, the resource was fulfilled via the cache.&lt;/span&gt;&lt;br /&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Time to first byte&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responseStart&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Start listening for `navigation` entries to be dispatched.&lt;/span&gt;&lt;br /&gt;  po&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do nothing if the browser doesn&#39;t support this API.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Another metric developers who use service worker may care about is the service
worker startup time for navigation requests. This is the amount of time it takes
the browser to start the service worker thread before it can start intercepting
fetch events.&lt;/p&gt;
&lt;p&gt;The service worker startup time for a particular navigation request can be
determined from the delta between &lt;code&gt;entry.responseStart&lt;/code&gt; and &lt;code&gt;entry.workerStart&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Catch errors since some browsers throw when using the new `type` option.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// https://bugs.webkit.org/show_bug.cgi?id=209216&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Create the performance observer.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; po &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Service Worker startup time:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responseStart &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;workerStart&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Start listening for `navigation` entries to be dispatched.&lt;/span&gt;&lt;br /&gt;  po&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do nothing if the browser doesn&#39;t support this API.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;server-timing-api&quot;&gt;Server Timing API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/custom-metrics/#server-timing-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://w3c.github.io/server-timing/&quot; rel=&quot;noopener&quot;&gt;Server Timing API&lt;/a&gt; allows you to pass
request-specific timing data from your server to the browser via response
headers. For example, you can indicate how long it took to lookup data in a
database for a particular request—which can be useful in debugging performance
issues caused by slowness on the server.&lt;/p&gt;
&lt;p&gt;For developers who use third-party analytics providers, the Server Timing API is
the only way to correlate server performance data with other business metrics
that these analytics tools may be measuring.&lt;/p&gt;
&lt;p&gt;To specify server timing data in your responses, you can use the &lt;code&gt;Server-Timing&lt;/code&gt;
response header. Here&#39;s an example.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-http&quot;&gt;&lt;code class=&quot;language-http&quot;&gt;&lt;span class=&quot;token response-status&quot;&gt;&lt;span class=&quot;token http-version property&quot;&gt;HTTP/1.1&lt;/span&gt; &lt;span class=&quot;token status-code number&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;token reason-phrase string&quot;&gt;OK&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token header&quot;&gt;&lt;span class=&quot;token header-name keyword&quot;&gt;Server-Timing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token header-value&quot;&gt;miss, db;dur=53, app;dur=47.2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Then, from your pages, you can read this data on both &lt;code&gt;resource&lt;/code&gt; or &lt;code&gt;navigation&lt;/code&gt;
entries from the Resource Timing and Navigation Timing APIs.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Catch errors since some browsers throw when using the new `type` option.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// https://bugs.webkit.org/show_bug.cgi?id=209216&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Create the performance observer.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; po &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Logs all server timing data for this response&lt;/span&gt;&lt;br /&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Server Timing&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serverTiming&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Start listening for `navigation` entries to be dispatched.&lt;/span&gt;&lt;br /&gt;  po&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;navigation&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do nothing if the browser doesn&#39;t support this API.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>First Input Delay (FID)</title>
    <link href="https://web.dev/fid/"/>
    <updated>2019-11-07T00:00:00Z</updated>
    <id>https://web.dev/fid/</id>
    <content type="html" mode="escaped">&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 76, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      76
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 89, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      89
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      79
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceEventTiming#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; First Input Delay (FID) is the &lt;a href=&quot;https://web.dev/vitals/#stable&quot;&gt;stable&lt;/a&gt; Core Web Vital metric for measuring load responsiveness because it quantifies the experience users feel when trying to interact with unresponsive pages—a low FID helps ensure that the page is usable. FID will be &lt;a href=&quot;https://web.dev/inp-cwv/&quot;&gt;replaced by Interaction to Next Paint (INP)&lt;/a&gt; as a Core Web Vital in March 2024. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;We all know how important it is to make a good first impression. It&#39;s important
when meeting new people, and it&#39;s also important when building experiences on
the web.&lt;/p&gt;
&lt;p&gt;On the web, a good first impression can make the difference between someone
becoming a loyal user or them leaving and never coming back. The question is,
what makes for a good impression, and how do you measure what kind of impression
you&#39;re likely making on your users?&lt;/p&gt;
&lt;p&gt;On the web, first impressions can take a lot of different forms—we have
first impressions of a site&#39;s design and visual appeal as well as first
impressions of its speed and responsiveness.&lt;/p&gt;
&lt;p&gt;While it is hard to measure how much users like a site&#39;s design with web APIs,
measuring its speed and responsiveness is not!&lt;/p&gt;
&lt;p&gt;The first impression users have of how fast your site loads can be measured with
&lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt;. But how fast your site can paint pixels
to the screen is just part of the story. Equally important is how responsive
your site is when users try to interact with those pixels!&lt;/p&gt;
&lt;p&gt;The First Input Delay (FID) metric helps measure your user&#39;s first impression of
your site&#39;s interactivity and responsiveness.&lt;/p&gt;
&lt;h2 id=&quot;what-is-fid&quot;&gt;What is FID? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#what-is-fid&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;FID measures the time from when a user first interacts with a page (that is, when
they click a link, tap on a button, or use a custom, JavaScript-powered control)
to the time when the browser is actually able to begin processing event handlers
in response to that interaction.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-good-fid-score&quot;&gt;What is a good FID score? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#what-is-a-good-fid-score&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To provide a good user experience, sites should strive to have a First Input
Delay of &lt;strong&gt;100 milliseconds&lt;/strong&gt; or less. To ensure you&#39;re hitting this target for
most of your users, a good threshold to measure is the &lt;strong&gt;75th percentile&lt;/strong&gt; of
page loads, segmented across mobile and desktop devices.&lt;/p&gt;
&lt;figure&gt;
  &lt;picture&gt;
    &lt;source srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eXyvkqRHQZ5iG38Axh1Z.svg&quot; media=&quot;(min-width: 640px)&quot; width=&quot;800&quot; height=&quot;200&quot; /&gt;
    &lt;img alt=&quot;Good FID values are 2.5 seconds or less, poor values are greater than 4.0 seconds, and anything in between needs improvement&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Se4TiXIdp8jtLJVScWed.svg&quot; width=&quot;640&quot; /&gt;
  &lt;/picture&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; To learn more about the research and methodology behind this recommendation, see: &lt;a href=&quot;https://web.dev/defining-core-web-vitals-thresholds/&quot;&gt;Defining the Core Web Vitals metrics thresholds&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;fid-in-detail&quot;&gt;FID in detail &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#fid-in-detail&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As developers who write code that responds to events, we often assume our code
is going to be run immediately—as soon as the event happens. But as users,
we&#39;ve all frequently experienced the opposite—we&#39;ve loaded a web page on
our phone, tried to interact with it, and then been frustrated when nothing
happened.&lt;/p&gt;
&lt;p&gt;In general, input delay (a.k.a. input latency) happens because the browser&#39;s
main thread is busy doing something else, so it can&#39;t (yet) respond to the user.
One common reason this might happen is the browser is busy parsing and executing
a large JavaScript file loaded by your app. While it&#39;s doing that, it can&#39;t run
any event listeners because the JavaScript it&#39;s loading might tell it to do
something else.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; FID only measures the &amp;quot;delay&amp;quot; in event processing. It does not measure the event processing time itself nor the time it takes the browser to update the UI after running event handlers. While this time does affect the user experience, including it as part of FID would incentivize developers to respond to events asynchronously—which would improve the metric but likely make the experience worse. See &lt;a href=&quot;https://web.dev/fid/#why-only-consider-the-input-delay&quot;&gt;why only consider the input delay&lt;/a&gt; below for more details. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Consider the following timeline of a typical web page load:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/admin/9tm3f6pwlHMqNKuFvaP0.svg&quot;&gt;&lt;img alt=&quot;Example page load trace&quot; decoding=&quot;async&quot; height=&quot;260&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/admin/9tm3f6pwlHMqNKuFvaP0.svg&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The above visualization shows a page that&#39;s making a couple of network requests
for resources (most likely CSS and JS files), and—after those resources
are finished downloading—they&#39;re processed on the main thread.&lt;/p&gt;
&lt;p&gt;This results in periods where the main thread is momentarily busy, which is
indicated by the beige-colored
&lt;a href=&quot;https://html.spec.whatwg.org/multipage/webappapis.html#concept-task&quot; rel=&quot;noopener&quot;&gt;task&lt;/a&gt;
blocks.&lt;/p&gt;
&lt;p&gt;Long first input delays typically occur between &lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful Paint
(FCP)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/tti/&quot;&gt;Time to Interactive (TTI)&lt;/a&gt; because the page has
rendered some of its content but isn&#39;t yet reliably interactive. To illustrate
how this can happen, FCP and TTI have been added to the timeline:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/admin/24Y3T5sWNuZD9fKhkuER.svg&quot;&gt;&lt;img alt=&quot;Example page load trace with FCP and TTI&quot; decoding=&quot;async&quot; height=&quot;340&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/admin/24Y3T5sWNuZD9fKhkuER.svg&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You may have noticed that there&#39;s a fair amount of time (including three &lt;a href=&quot;https://web.dev/custom-metrics/#long-tasks-api&quot;&gt;long
tasks&lt;/a&gt;) between FCP and TTI, if a user tries to
interact with the page during that time (for example, by clicking on a link), there will be a
delay between when the click is received and when the main thread is able to
respond.&lt;/p&gt;
&lt;p&gt;Consider what would happen if a user tried to interact with the page near the
beginning of the longest task:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/admin/krOoeuQ4TWCbt9t6v5Wf.svg&quot;&gt;&lt;img alt=&quot;Example page load trace with FCP, TTI, and FID&quot; decoding=&quot;async&quot; height=&quot;380&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/admin/krOoeuQ4TWCbt9t6v5Wf.svg&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Because the input occurs while the browser is in the middle of running a task,
it has to wait until the task completes before it can respond to the input. The
time it must wait is the FID value for this user on this page.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; In this example the user just happened to interact with the page at the beginning of the main thread&#39;s most busy period. If the user had interacted with the page just a moment earlier (during the idle period) the browser could have responded right away. This variance in input delay underscores the importance of looking at the distribution of FID values when reporting on the metric. You can read more about this in the section below on analyzing and reporting on FID data. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;what-if-an-interaction-doesnt-have-an-event-listener&quot;&gt;What if an interaction doesn&#39;t have an event listener? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#what-if-an-interaction-doesnt-have-an-event-listener&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;FID measures the delta between when an input event is received and when the main
thread is next idle. This means FID is measured &lt;strong&gt;even in cases where an event
listener has not been registered.&lt;/strong&gt; The reason is because many user interactions
do not require an event listener but &lt;em&gt;do&lt;/em&gt; require the main thread to be idle in
order to run.&lt;/p&gt;
&lt;p&gt;For example, all of the following HTML elements need to wait for
in-progress tasks on the main thread to complete prior to responding to user
interactions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Text fields, checkboxes, and radio buttons (&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Select dropdowns (&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;links (&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-only-consider-the-first-input&quot;&gt;Why only consider the first input? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#why-only-consider-the-first-input&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While a delay from any input can lead to a bad user experience, we primarily
recommend measuring the first input delay for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first input delay will be the user&#39;s first impression of your site&#39;s
responsiveness, and first impressions are critical in shaping our overall
impression of a site&#39;s quality and reliability.&lt;/li&gt;
&lt;li&gt;The biggest interactivity issues we see on the web today occur during page
load. Therefore, we believe initially focusing on improving site&#39;s first user
interaction will have the greatest impact on improving the overall
interactivity of the web.&lt;/li&gt;
&lt;li&gt;The recommended solutions for how sites should fix high first input delays
(code splitting, loading less JavaScript upfront, etc.) are not necessarily
the same solutions for fixing slow input delays after page load. By separating
out these metrics we&#39;ll be able to provide more specific performance
guidelines to web developers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-counts-as-a-first-input&quot;&gt;What counts as a first input? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#what-counts-as-a-first-input&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;FID is a metric that measures a page&#39;s responsiveness during load. As such, it
only focuses on input events from discrete actions like clicks, taps, and key
presses.&lt;/p&gt;
&lt;p&gt;Other interactions, like scrolling and zooming, are continuous actions and have
completely different performance constraints (also, browsers are often able to
hide their latency by running them on a separate thread).&lt;/p&gt;
&lt;p&gt;To put this another way, FID focuses on the R (responsiveness) in the &lt;a href=&quot;https://web.dev/rail/&quot;&gt;RAIL
performance
model&lt;/a&gt;, whereas
scrolling and zooming are more related to A (animation), and their performance
qualities should be evaluated separately.&lt;/p&gt;
&lt;h3 id=&quot;what-if-a-user-never-interacts-with-your-site&quot;&gt;What if a user never interacts with your site? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#what-if-a-user-never-interacts-with-your-site&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Not all users will interact with your site every time they visit. And not all
interactions are relevant to FID (as mentioned in the previous section). In
addition, some user&#39;s first interactions will be at bad times (when the main
thread is busy for an extended period of time), and some user&#39;s first
interactions will be at good times (when the main thread is completely idle).&lt;/p&gt;
&lt;p&gt;This means some users will have no FID values, some users will have low FID
values, and some users will probably have high FID values.&lt;/p&gt;
&lt;p&gt;How you track, report on, and analyze FID will probably be quite a bit different
from other metrics you may be used to. The next section explains how best to do
this.&lt;/p&gt;
&lt;h3 id=&quot;why-only-consider-the-input-delay&quot;&gt;Why only consider the input delay? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#why-only-consider-the-input-delay&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As mentioned above, FID only measures the &amp;quot;delay&amp;quot; in event processing. It does
not measure the event processing time itself nor the time it takes the browser
to update the UI after running event handlers.&lt;/p&gt;
&lt;p&gt;Even though this time is important to the user and &lt;em&gt;does&lt;/em&gt; affect the experience,
it&#39;s not included in this metric because doing so could incentivize developers
to add workarounds that actually make the experience worse—that is, they
could wrap their event handler logic in an asynchronous callback (via
&lt;code&gt;setTimeout()&lt;/code&gt; or &lt;code&gt;requestAnimationFrame()&lt;/code&gt;) in order to separate it from the
task associated with the event. The result would be an improvement in the metric
score but a slower response as perceived by the user.&lt;/p&gt;
&lt;p&gt;However, while FID only measure the &amp;quot;delay&amp;quot; portion of event latency, developers
who want to track more of the event lifecycle can do so using the &lt;a href=&quot;https://wicg.github.io/event-timing/&quot; rel=&quot;noopener&quot;&gt;Event Timing
API&lt;/a&gt;. See the guide on &lt;a href=&quot;https://web.dev/custom-metrics/#event-timing-api&quot;&gt;custom
metrics&lt;/a&gt; for more details.&lt;/p&gt;
&lt;h2 id=&quot;how-to-measure-fid&quot;&gt;How to measure FID &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#how-to-measure-fid&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;FID is a metric that can only be measured &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;in the
field&lt;/a&gt;, as it requires a real
user to interact with your page. You can measure FID with the following tools.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; FID requires a real user and thus cannot be measured in the lab. However, the &lt;a href=&quot;https://web.dev/tbt/&quot;&gt;Total Blocking Time (TBT)&lt;/a&gt; metric is lab-measurable, correlates well with FID in the field, and also captures issues that affect interactivity. Optimizations that improve TBT in the lab should also improve FID for your users. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;field-tools&quot;&gt;Field tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#field-tools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience
Report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed Insights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/webmasters/answer/9205520&quot; rel=&quot;noopener&quot;&gt;Search Console (Core Web Vitals
report)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;measure-fid-in-javascript&quot;&gt;Measure FID in JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#measure-fid-in-javascript&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To measure FID in JavaScript, you can use the &lt;a href=&quot;https://wicg.github.io/event-timing&quot; rel=&quot;noopener&quot;&gt;Event Timing
API&lt;/a&gt;. The following example shows how to
create a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;PerformanceObserver&lt;/code&gt;&lt;/a&gt;
that listens for
&lt;a href=&quot;https://wicg.github.io/event-timing/#sec-performance-event-timing&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;first-input&lt;/code&gt;&lt;/a&gt;
entries and logs them to the console:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entryList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; delay &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processingStart &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;FID candidate:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delay&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;first-input&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; This code shows how to log &lt;code&gt;first-input&lt;/code&gt; entries to the console and calculate their delay. However, measuring FID in JavaScript is more complicated. See below for details: &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;In the above example, the &lt;code&gt;first-input&lt;/code&gt; entry&#39;s delay value is measured by
taking the delta between the entry&#39;s &lt;code&gt;startTime&lt;/code&gt; and &lt;code&gt;processingStart&lt;/code&gt;
timestamps. In most cases this will be the FID value; however, not all
&lt;code&gt;first-input&lt;/code&gt; entries are valid for measuring FID.&lt;/p&gt;
&lt;p&gt;The following section lists the differences between what the API reports and how
the metric is calculated.&lt;/p&gt;
&lt;h4 id=&quot;differences-between-the-metric-and-the-api&quot;&gt;Differences between the metric and the API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#differences-between-the-metric-and-the-api&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;The API will dispatch &lt;code&gt;first-input&lt;/code&gt; entries for pages loaded
in a background tab but those pages should be ignored when calculating FID.&lt;/li&gt;
&lt;li&gt;The API will also dispatch &lt;code&gt;first-input&lt;/code&gt; entries if the page was backgrounded
prior to the first input occurring, but those pages should also be ignored
when calculating FID (inputs are only considered if the page was in the
foreground the entire time).&lt;/li&gt;
&lt;li&gt;The API does not report &lt;code&gt;first-input&lt;/code&gt; entries when the page is restored from
the &lt;a href=&quot;https://web.dev/bfcache/#impact-on-core-web-vitals&quot;&gt;back/forward cache&lt;/a&gt;, but FID should
be measured in these cases since users experience them as distinct page
visits.&lt;/li&gt;
&lt;li&gt;The API does not report inputs that occur within iframes but the metric does
as they are part of the user experience of the page. This can
&lt;a href=&quot;https://web.dev/crux-and-rum-differences/#iframes&quot;&gt;show as a difference between CrUX and RUM&lt;/a&gt;.
To properly measure FID you should consider them. Sub-frames can use the API
to report their &lt;code&gt;first-input&lt;/code&gt; entries to the parent frame for aggregation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rather than memorizing all these subtle differences, developers can use the
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt; to
measure FID, which handles these differences for you (where possible):&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onFID&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;web-vitals&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Measure and log FID as soon as it&#39;s available.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onFID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can refer to &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/blob/main/src/onFID.ts&quot; rel=&quot;noopener&quot;&gt;the source code for
&lt;code&gt;onFID()&lt;/code&gt;&lt;/a&gt;
for a complete example of how to measure FID in JavaScript.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; In some cases (such as cross-origin iframes) it&#39;s not possible to measure FID in JavaScript. See the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#limitations&quot;&gt;limitations&lt;/a&gt; section of the &lt;code&gt;web-vitals&lt;/code&gt; library for details. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;analyzing-and-reporting-on-fid-data&quot;&gt;Analyzing and reporting on FID data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#analyzing-and-reporting-on-fid-data&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Due to the expected variance in FID values, it&#39;s critical that when reporting on
FID you look at the distribution of values and focus on the higher percentiles.&lt;/p&gt;
&lt;p&gt;While &lt;a href=&quot;https://web.dev/defining-core-web-vitals-thresholds/#choice-of-percentile&quot;&gt;choice of
percentile&lt;/a&gt; for all
Core Web Vitals thresholds is the 75th, for FID in particular we still strongly
recommend looking at the 95th–99th percentiles, as those will correspond to the
particularly bad first experiences users are having with your site. And it will
show you the areas that need the most improvement.&lt;/p&gt;
&lt;p&gt;This is true even if you segment your reports by device category or type. For
example, if you run separate reports for desktop and mobile, the FID value you
care most about on desktop should be the 95th–99th percentile of desktop users,
and the FID value you care about most on mobile should be the 95th–99th
percentile of mobile users.&lt;/p&gt;
&lt;h2 id=&quot;how-to-improve-fid&quot;&gt;How to improve FID &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#how-to-improve-fid&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A full guide on &lt;a href=&quot;https://web.dev/optimize-fid/&quot;&gt;optimizing FID&lt;/a&gt; is available to guide you through techniques to improve this metric.&lt;/p&gt;
&lt;h2 id=&quot;changelog&quot;&gt;CHANGELOG &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fid/#changelog&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Occasionally, bugs are discovered in the APIs used to measure metrics, and sometimes in the definitions of the metrics themselves. As a result, changes must sometimes be made, and these changes can show up as improvements or regressions in your internal reports and dashboards.&lt;/p&gt;
&lt;p&gt;To help you manage this, all changes to either the implementation or definition of these metrics will be surfaced in this &lt;a href=&quot;http://bit.ly/chrome-speed-metrics-changelog&quot; rel=&quot;noopener&quot;&gt;CHANGELOG&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have feedback for these metrics, you can provide it in the &lt;a href=&quot;https://groups.google.com/g/web-vitals-feedback&quot; rel=&quot;noopener&quot;&gt;web-vitals-feedback Google group&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>First Contentful Paint (FCP)</title>
    <link href="https://web.dev/fcp/"/>
    <updated>2019-11-07T00:00:00Z</updated>
    <id>https://web.dev/fcp/</id>
    <content type="html" mode="escaped">&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 60, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      60
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 84, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      84
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      79
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 14.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      14.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/PerformancePaintTiming#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; First Contentful Paint (FCP) is an important, user-centric metric for measuring &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#types-of-metrics&quot;&gt;perceived load speed&lt;/a&gt; because it marks the first point in the page load timeline where the user can see anything on the screen—a fast FCP helps reassure the user that something is &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#questions&quot;&gt;happening&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;what-is-fcp&quot;&gt;What is FCP? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fcp/#what-is-fcp&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The First Contentful Paint (FCP) metric measures the time from when the page
starts loading to when any part of the page&#39;s content is rendered on the screen.
For this metric, &amp;quot;content&amp;quot; refers to text, images (including background images),
&lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; elements, or non-white &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; elements.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&quot;&gt;&lt;img alt=&quot;FCP timeline from google.com&quot; decoding=&quot;async&quot; height=&quot;311&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/3UhlOxRc0j8Vc4DGd4dt.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the above load timeline, FCP happens in the second
frame, as that&#39;s when the first text and image elements are rendered to the
screen.&lt;/p&gt;
&lt;p&gt;You&#39;ll notice that though some of the content has rendered, not all of it has
rendered. This is an important distinction to make between &lt;em&gt;First&lt;/em&gt; Contentful
Paint (FCP) and &lt;em&gt;&lt;a href=&quot;https://web.dev/lcp/&quot;&gt;Largest Contentful Paint (LCP)&lt;/a&gt;&lt;/em&gt;
—which aims to measure when the page&#39;s main contents have finished
loading.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-good-fcp-score&quot;&gt;What is a good FCP score? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fcp/#what-is-a-good-fcp-score&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To provide a good user experience, sites should strive to have a First
Contentful Paint of &lt;strong&gt;1.8 seconds&lt;/strong&gt; or less. To ensure you&#39;re hitting this
target for most of your users, a good threshold to measure is the &lt;strong&gt;75th
percentile&lt;/strong&gt; of page loads, segmented across mobile and desktop devices.&lt;/p&gt;
&lt;figure&gt;
  &lt;picture&gt;
    &lt;source srcset=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/V1mtKJenViYAhn05WxqR.svg&quot; media=&quot;(min-width: 640px)&quot; width=&quot;800&quot; height=&quot;200&quot; /&gt;
    &lt;img alt=&quot;Good FCP values are 1.8 seconds or less, poor values are greater than 3.0 seconds, and anything in between needs improvement&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/vQKpz0S2SGnnoXHMDidj.svg&quot; width=&quot;640&quot; /&gt;
  &lt;/picture&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;how-to-measure-fcp&quot;&gt;How to measure FCP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fcp/#how-to-measure-fcp&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;FCP can be measured &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;in the lab&lt;/a&gt;
or &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;in the field&lt;/a&gt;, and it&#39;s
available in the following tools:&lt;/p&gt;
&lt;h3 id=&quot;field-tools&quot;&gt;Field tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fcp/#field-tools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed Insights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience
Report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webmasters.googleblog.com/2019/11/search-console-speed-report.html&quot; rel=&quot;noopener&quot;&gt;Search Console (Speed
Report)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;lab-tools&quot;&gt;Lab tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fcp/#lab-tools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/&quot; rel=&quot;noopener&quot;&gt;Chrome DevTools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed Insights&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;measure-fcp-in-javascript&quot;&gt;Measure FCP in JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fcp/#measure-fcp-in-javascript&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To measure FCP in JavaScript, you can use the &lt;a href=&quot;https://w3c.github.io/paint-timing/&quot; rel=&quot;noopener&quot;&gt;Paint Timing
API&lt;/a&gt;. The following example shows how to
create a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;PerformanceObserver&lt;/code&gt;&lt;/a&gt;
that listens for a &lt;code&gt;paint&lt;/code&gt; entry with the name &lt;code&gt;first-contentful-paint&lt;/code&gt; and logs
it to the console.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entryList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntriesByName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;first-contentful-paint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;FCP candidate:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;paint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt;  This code shows how to log the &lt;code&gt;first-contentful-paint&lt;/code&gt; entry to the console, but measuring FCP in JavaScript is more complicated. See below for details:  &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;In the above example, the logged &lt;code&gt;first-contentful-paint&lt;/code&gt; entry will tell you
when the first contentful element was painted. However, in some cases this entry
is not valid for measuring FCP.&lt;/p&gt;
&lt;p&gt;The following section lists the differences between what the API reports and how
the metric is calculated.&lt;/p&gt;
&lt;h4 id=&quot;differences-between-the-metric-and-the-api&quot;&gt;Differences between the metric and the API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fcp/#differences-between-the-metric-and-the-api&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;The API will dispatch a &lt;code&gt;first-contentful-paint&lt;/code&gt; entry for pages loaded in a
background tab, but those pages should be ignored when calculating FCP (first
paint timings should only be considered if the page was in the foreground the
entire time).&lt;/li&gt;
&lt;li&gt;The API does not report &lt;code&gt;first-contentful-paint&lt;/code&gt; entries when the page is
restored from the &lt;a href=&quot;https://web.dev/bfcache/#impact-on-core-web-vitals&quot;&gt;back/forward cache&lt;/a&gt;,
but FCP should be measured in these cases since users experience them as
distinct page visits.&lt;/li&gt;
&lt;li&gt;The API &lt;a href=&quot;https://w3c.github.io/paint-timing/#:~:text=cross-origin%20iframes&quot; rel=&quot;noopener&quot;&gt;may not report paint timings from cross-origin
iframes&lt;/a&gt;,
but to properly measure FCP you should consider all frames. Sub-frames can use
the API to report their paint timings to the parent frame for aggregation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rather than memorizing all these subtle differences, developers can use the
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt; to
measure FCP, which handles these differences for you (where possible):&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onFCP&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;web-vitals&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Measure and log FCP as soon as it&#39;s available.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onFCP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can refer to &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/blob/main/src/onFCP.ts&quot; rel=&quot;noopener&quot;&gt;the source code for
&lt;code&gt;onFCP()&lt;/code&gt;&lt;/a&gt;
for a complete example of how to measure FCP in JavaScript.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; In some cases (such as cross-origin iframes) it&#39;s not possible to measure FCP in JavaScript. See the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#limitations&quot;&gt;limitations&lt;/a&gt; section of the &lt;code&gt;web-vitals&lt;/code&gt; library for details. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;how-to-improve-fcp&quot;&gt;How to improve FCP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fcp/#how-to-improve-fcp&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To learn how to improve FCP for a specific site, you can run a Lighthouse
performance audit and pay attention to any specific
&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/#opportunities&quot; rel=&quot;noopener&quot;&gt;opportunities&lt;/a&gt; or
&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/#diagnostics&quot; rel=&quot;noopener&quot;&gt;diagnostics&lt;/a&gt; the audit suggests.&lt;/p&gt;
&lt;p&gt;To learn how to improve FCP in general (for any site), refer to the following
performance guides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/render-blocking-resources/&quot; rel=&quot;noopener&quot;&gt;Eliminate render-blocking resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/unminified-css/&quot; rel=&quot;noopener&quot;&gt;Minify CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/unused-css-rules/&quot; rel=&quot;noopener&quot;&gt;Remove unused CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/unused-javascript/&quot; rel=&quot;noopener&quot;&gt;Remove unused JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/&quot; rel=&quot;noopener&quot;&gt;Preconnect to required origins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/ttfb/&quot;&gt;Reduce server response times (TTFB)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/redirects/&quot; rel=&quot;noopener&quot;&gt;Avoid multiple page redirects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preload/&quot; rel=&quot;noopener&quot;&gt;Preload key requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/total-byte-weight/&quot; rel=&quot;noopener&quot;&gt;Avoid enormous network payloads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-long-cache-ttl/&quot; rel=&quot;noopener&quot;&gt;Serve static assets with an efficient cache policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/dom-size/&quot; rel=&quot;noopener&quot;&gt;Avoid an excessive DOM size&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains/&quot; rel=&quot;noopener&quot;&gt;Minimize critical request depth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/font-display/&quot; rel=&quot;noopener&quot;&gt;Ensure text remains visible during webfont load&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/resource-summary/&quot; rel=&quot;noopener&quot;&gt;Keep request counts low and transfer sizes small&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changelog&quot;&gt;CHANGELOG &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/fcp/#changelog&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Occasionally, bugs are discovered in the APIs used to measure metrics, and sometimes in the definitions of the metrics themselves. As a result, changes must sometimes be made, and these changes can show up as improvements or regressions in your internal reports and dashboards.&lt;/p&gt;
&lt;p&gt;To help you manage this, all changes to either the implementation or definition of these metrics will be surfaced in this &lt;a href=&quot;http://bit.ly/chrome-speed-metrics-changelog&quot; rel=&quot;noopener&quot;&gt;CHANGELOG&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have feedback for these metrics, you can provide it in the &lt;a href=&quot;https://groups.google.com/g/web-vitals-feedback&quot; rel=&quot;noopener&quot;&gt;web-vitals-feedback Google group&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>Total Blocking Time (TBT)</title>
    <link href="https://web.dev/tbt/"/>
    <updated>2019-11-07T00:00:00Z</updated>
    <id>https://web.dev/tbt/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt;  Total Blocking Time (TBT) is an important &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;lab metric&lt;/a&gt; for measuring &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#types-of-metrics&quot;&gt;load responsiveness&lt;/a&gt; because it helps quantify the severity of how non-interactive a page is prior to it becoming reliably interactive—a low TBT helps ensure that the page is &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#questions&quot;&gt;usable&lt;/a&gt;.  &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;what-is-tbt&quot;&gt;What is TBT? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tbt/#what-is-tbt&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Total Blocking Time (TBT) metric measures the total amount of time between
&lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/tti/&quot;&gt;Time to Interactive (TTI)&lt;/a&gt;
where the main thread was blocked for long enough to prevent input
responsiveness.&lt;/p&gt;
&lt;p&gt;The main thread is considered &amp;quot;blocked&amp;quot; any time there&#39;s a &lt;a href=&quot;https://web.dev/custom-metrics/#long-tasks-api&quot;&gt;Long
Task&lt;/a&gt;—a task that runs on the main
thread for more than 50 milliseconds (ms). We say the main thread is &amp;quot;blocked&amp;quot;
because the browser cannot interrupt a task that&#39;s in progress. So in the event
that a user &lt;em&gt;does&lt;/em&gt; interact with the page in the middle of a long task, the
browser must wait for the task to finish before it can respond.&lt;/p&gt;
&lt;p&gt;If the task is long enough (anything above 50 ms), it&#39;s likely that the
user will notice the delay and perceive the page as sluggish or janky.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;blocking time&lt;/em&gt; of a given long task is its duration in excess of 50 ms. And
the &lt;em&gt;total blocking time&lt;/em&gt; for a page is the sum of the &lt;em&gt;blocking time&lt;/em&gt; for each
long task that occurs between FCP and TTI.&lt;/p&gt;
&lt;p&gt;For example, consider the following diagram of the browser&#39;s main thread during
page load:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/admin/clHG8Yv239lXsGWD6Iu6.svg&quot;&gt;&lt;img alt=&quot;A tasks timeline on the main thread&quot; decoding=&quot;async&quot; height=&quot;156&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/admin/clHG8Yv239lXsGWD6Iu6.svg&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The above timeline has five tasks, three of which are Long Tasks because their
duration exceeds 50 ms. The next diagram shows the blocking time for each of the
long tasks:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xKxwKagiz8RliuOI2Xtc.svg&quot;&gt;&lt;img alt=&quot;A tasks timeline on the main thread showing blocking time&quot; decoding=&quot;async&quot; height=&quot;156&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xKxwKagiz8RliuOI2Xtc.svg&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So while the total time spent running tasks on the main thread is 560 ms, only
345 ms of that time is considered blocking time.&lt;/p&gt;
&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;&lt;/th&gt;
    &lt;th&gt;Task duration&lt;/th&gt;
    &lt;th&gt;Task blocking time&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Task one&lt;/td&gt;
    &lt;td&gt;250 ms&lt;/td&gt;
    &lt;td&gt;200 ms&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Task two&lt;/td&gt;
    &lt;td&gt;90 ms&lt;/td&gt;
    &lt;td&gt;40 ms&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Task three&lt;/td&gt;
    &lt;td&gt;35 ms&lt;/td&gt;
    &lt;td&gt;0 ms&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Task four&lt;/td&gt;
    &lt;td&gt;30 ms&lt;/td&gt;
    &lt;td&gt;0 ms&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Task five&lt;/td&gt;
    &lt;td&gt;155 ms&lt;/td&gt;
    &lt;td&gt;105 ms&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td colspan=&quot;2&quot;&gt;&lt;strong&gt;Total Blocking Time&lt;/strong&gt;&lt;/td&gt;
    &lt;td&gt;&lt;strong&gt;345 ms&lt;/strong&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;h3 id=&quot;how-does-tbt-relate-to-tti&quot;&gt;How does TBT relate to TTI? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tbt/#how-does-tbt-relate-to-tti&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;TBT is a great companion metric for TTI because it helps quantify the severity
of how non-interactive a page is prior it to becoming reliably interactive.&lt;/p&gt;
&lt;p&gt;TTI considers a page &amp;quot;reliably interactive&amp;quot; if the main thread has been free of
long tasks for at least five seconds. This means that three, 51 ms tasks spread
out over 10 seconds can push back TTI just as far as a single 10-second long
task—but those two scenarios would feel very different to a user trying to
interact with the page.&lt;/p&gt;
&lt;p&gt;In the first case, three, 51 ms tasks would have a TBT of &lt;strong&gt;3 ms&lt;/strong&gt;. Whereas a
single, 10-second long tasks would have a TBT of &lt;strong&gt;9950 ms&lt;/strong&gt;. The larger TBT
value in the second case quantifies the worse experience.&lt;/p&gt;
&lt;h2 id=&quot;how-to-measure-tbt&quot;&gt;How to measure TBT &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tbt/#how-to-measure-tbt&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;TBT is a metric that should be measured &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;in the
lab&lt;/a&gt;. The best way to measure TBT is to run a
Lighthouse performance audit on your site. See the &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/&quot; rel=&quot;noopener&quot;&gt;Lighthouse documentation on
TBT&lt;/a&gt; for usage details.&lt;/p&gt;
&lt;h3 id=&quot;lab-tools&quot;&gt;Lab tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tbt/#lab-tools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/&quot; rel=&quot;noopener&quot;&gt;Chrome DevTools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.webpagetest.org/&quot; rel=&quot;noopener&quot;&gt;WebPageTest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; While it is possible to measure TBT in the field, it&#39;s not recommended as user interaction can affect your page&#39;s TBT in ways that lead to lots of variance in your reports. To understand a page&#39;s interactivity in the field, you should measure &lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/inp/&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;what-is-a-good-tbt-score&quot;&gt;What is a good TBT score? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tbt/#what-is-a-good-tbt-score&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To provide a good user experience, sites should strive to have a Total Blocking
Time of less than &lt;strong&gt;200 milliseconds&lt;/strong&gt; when tested on &lt;strong&gt;average mobile
hardware&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For details on how your page&#39;s TBT affects your Lighthouse performance score,
see &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time/#how-lighthouse-determines-your-tbt-score&quot; rel=&quot;noopener&quot;&gt;How Lighthouse determines your TBT
score&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;how-to-improve-tbt&quot;&gt;How to improve TBT &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tbt/#how-to-improve-tbt&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To learn how to improve TBT for a specific site, you can run a Lighthouse
performance audit and pay attention to any specific
&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/#opportunities&quot; rel=&quot;noopener&quot;&gt;opportunities&lt;/a&gt; the audit suggests.&lt;/p&gt;
&lt;p&gt;To learn how to improve TBT in general (for any site), refer to the following
performance guides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/third-party-summary/&quot; rel=&quot;noopener&quot;&gt;Reduce the impact of third-party code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/bootup-time/&quot; rel=&quot;noopener&quot;&gt;Reduce JavaScript execution time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/mainthread-work-breakdown/&quot; rel=&quot;noopener&quot;&gt;Minimize main thread work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/resource-summary/&quot; rel=&quot;noopener&quot;&gt;Keep request counts low and transfer sizes small&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>Time to Interactive (TTI)</title>
    <link href="https://web.dev/tti/"/>
    <updated>2019-11-07T00:00:00Z</updated>
    <id>https://web.dev/tti/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Time to Interactive (TTI) is an important &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;lab metric&lt;/a&gt; for measuring &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#types-of-metrics&quot;&gt;load responsiveness&lt;/a&gt;. It helps identify cases where a page &lt;em&gt;looks&lt;/em&gt; interactive but actually isn&#39;t. A fast TTI helps ensure that the page is &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#questions&quot;&gt;usable&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;what-is-tti&quot;&gt;What is TTI? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tti/#what-is-tti&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The TTI metric measures the time from when the page starts
loading to when its main sub-resources have loaded and it is capable of reliably
responding to user input quickly.&lt;/p&gt;
&lt;p&gt;To calculate TTI based on a &lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance/reference/&quot; rel=&quot;noopener&quot;&gt;performance
trace&lt;/a&gt;
of a web page, follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start at &lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Search forward in time for a quiet window of at least five seconds, where
&lt;em&gt;quiet window&lt;/em&gt; is defined as: no &lt;a href=&quot;https://web.dev/custom-metrics/#long-tasks-api&quot;&gt;long
tasks&lt;/a&gt; and no more than two in-flight
network GET requests.&lt;/li&gt;
&lt;li&gt;Search backwards for the last long task before the quiet window, stopping at
FCP if no long tasks are found.&lt;/li&gt;
&lt;li&gt;TTI is the end time of the last long task before the quiet window (or the
same value as FCP if no long tasks are found).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following diagram should help visualize the steps above:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/admin/WZM0n4aXah67lEyZugOT.svg&quot;&gt;&lt;img alt=&quot;A page load timeline showing how to compute TTI&quot; decoding=&quot;async&quot; height=&quot;473&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/admin/WZM0n4aXah67lEyZugOT.svg&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Historically, developers have optimized pages for fast render times,
sometimes at the expense of TTI.&lt;/p&gt;
&lt;p&gt;Techniques like server-side rendering (SSR) can lead to scenarios where a page
&lt;em&gt;looks&lt;/em&gt; interactive (that is, links and buttons are visible on the screen), but it&#39;s not
&lt;em&gt;actually&lt;/em&gt; interactive because the main thread is blocked or
because the JavaScript code controlling those elements hasn&#39;t loaded.&lt;/p&gt;
&lt;p&gt;When users try to interact with a page that looks interactive but actually
isn&#39;t, they&#39;ll likely respond in one of two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In the best-case scenario, they&#39;ll be annoyed that the page is slow to respond.&lt;/li&gt;
&lt;li&gt;In the worst-case scenario, they&#39;ll assume the page is broken and likely
leave. They may even lose confidence or trust in the value of your brand.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To avoid this problem, make every effort to minimize the difference
between FCP and TTI. And in cases where a noticeable difference does exist,
make it clear through visual indicators that the components on your page are not yet
interactive.&lt;/p&gt;
&lt;h2 id=&quot;how-to-measure-tti&quot;&gt;How to measure TTI &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tti/#how-to-measure-tti&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;TTI is a metric that&#39;s best measured &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;in the
lab&lt;/a&gt;. The best way to measure TTI is to run a
Lighthouse performance audit on your site. See the &lt;a href=&quot;https://web.dev/tti/&quot;&gt;Lighthouse documentation on
TTI&lt;/a&gt; for usage details.&lt;/p&gt;
&lt;h3 id=&quot;lab-tools&quot;&gt;Lab tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tti/#lab-tools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.webpagetest.org/&quot; rel=&quot;noopener&quot;&gt;WebPageTest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; While it&#39;s possible to measure TTI in the field, it&#39;s not recommended, as user interaction can affect your page&#39;s TTI in ways that lead to lots of variance in your reports. To understand a page&#39;s interactivity in the field, you should measure &lt;a href=&quot;https://web.dev/fid/&quot;&gt;First Input Delay (FID)&lt;/a&gt; and &lt;a href=&quot;https://web.dev/inp/&quot;&gt;Interaction to Next Paint (INP)&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;what-is-a-good-tti-score&quot;&gt;What is a good TTI score? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tti/#what-is-a-good-tti-score&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To provide a good user experience, sites should strive to have a Time to
Interactive of less than &lt;strong&gt;5 seconds&lt;/strong&gt; when tested on &lt;strong&gt;average mobile
hardware&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For details on how your page&#39;s TTI affects your Lighthouse performance score,
see &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/interactive/#how-lighthouse-determines-your-tti-score&quot; rel=&quot;noopener&quot;&gt;How Lighthouse determines your TTI
score&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-to-improve-tti&quot;&gt;How to improve TTI &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tti/#how-to-improve-tti&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To learn how to improve TTI for a specific site, you can run a Lighthouse
performance audit and pay attention to any specific
&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/#opportunities&quot; rel=&quot;noopener&quot;&gt;opportunities&lt;/a&gt; the audit suggests.&lt;/p&gt;
&lt;p&gt;To learn how to improve TTI in general (for any site), refer to the following
performance guides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/unminified-javascript/e.com/docs/lighthouse/performance/unminified-javascript/&quot; rel=&quot;noopener&quot;&gt;Minify JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preconnect/&quot; rel=&quot;noopener&quot;&gt;Preconnect to required origins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/uses-rel-preload/&quot; rel=&quot;noopener&quot;&gt;Preload key requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/third-party-summary/&quot; rel=&quot;noopener&quot;&gt;Reduce the impact of third-party code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains/&quot; rel=&quot;noopener&quot;&gt;Minimize critical request depth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/bootup-time/&quot; rel=&quot;noopener&quot;&gt;Reduce JavaScript execution time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/mainthread-work-breakdown/&quot; rel=&quot;noopener&quot;&gt;Minimize main thread work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/resource-summary/&quot; rel=&quot;noopener&quot;&gt;Keep request counts low and transfer sizes small&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>Largest Contentful Paint (LCP)</title>
    <link href="https://web.dev/lcp/"/>
    <updated>2019-08-08T00:00:00Z</updated>
    <id>https://web.dev/lcp/</id>
    <content type="html" mode="escaped">&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 77, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      77
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
79
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/LargestContentfulPaint#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Largest Contentful Paint (LCP) is an important, &lt;a href=&quot;https://web.dev/vitals/#stable&quot;&gt;stable&lt;/a&gt; Core Web Vital metric for measuring &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#types-of-metrics&quot;&gt;perceived load speed&lt;/a&gt; because it marks the point in the page load timeline when the page&#39;s main content has likely loaded—a fast LCP helps reassure the user that the page is &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#questions&quot;&gt;useful&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Historically, it&#39;s been a challenge for web developers to measure how quickly
the main content of a web page loads and is visible to users.&lt;/p&gt;
&lt;p&gt;Older metrics like
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/Events/load&quot; rel=&quot;noopener&quot;&gt;load&lt;/a&gt; or
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/Events/DOMContentLoaded&quot; rel=&quot;noopener&quot;&gt;DOMContentLoaded&lt;/a&gt;
are not good because they don&#39;t necessarily correspond to what the user sees on
their screen. And newer, user-centric performance metrics like &lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful
Paint (FCP)&lt;/a&gt; only capture the very beginning of the loading experience.
If a page shows a splash screen or displays a loading indicator, this moment is
not very relevant to the user.&lt;/p&gt;
&lt;p&gt;In the past we&#39;ve recommended performance metrics like &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/first-meaningful-paint/&quot; rel=&quot;noopener&quot;&gt;First Meaningful Paint
(FMP)&lt;/a&gt; and &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/speed-index/&quot; rel=&quot;noopener&quot;&gt;Speed Index (SI)&lt;/a&gt; (both
available in Lighthouse) to help capture more of the loading experience after
the initial paint, but these metrics are complex, hard to explain, and often
wrong—meaning they still do not identify when the main content of the page
has loaded.&lt;/p&gt;
&lt;p&gt;Sometimes simpler is better. Based on discussions in the &lt;a href=&quot;https://www.w3.org/webperf/&quot; rel=&quot;noopener&quot;&gt;W3C Web
Performance Working Group&lt;/a&gt; and research done at
Google, we&#39;ve found that a more accurate way to measure when the main content
of a page is loaded is to look at when the largest element was rendered.&lt;/p&gt;
&lt;h2 id=&quot;what-is-lcp&quot;&gt;What is LCP? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#what-is-lcp&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Largest Contentful Paint (LCP) metric reports the render time of the largest
&lt;a href=&quot;https://web.dev/lcp/#what-elements-are-considered&quot;&gt;image or text block&lt;/a&gt; visible within the
viewport, relative to when the page &lt;a href=&quot;https://w3c.github.io/hr-time/#timeorigin-attribute&quot; rel=&quot;noopener&quot;&gt;first started
loading&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-good-lcp-score&quot;&gt;What is a good LCP score? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#what-is-a-good-lcp-score&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To provide a good user experience, sites should strive to have Largest
Contentful Paint of &lt;strong&gt;2.5 seconds&lt;/strong&gt; or less. To ensure you&#39;re hitting this
target for most of your users, a good threshold to measure is the &lt;strong&gt;75th
percentile&lt;/strong&gt; of page loads, segmented across mobile and desktop devices.&lt;/p&gt;
&lt;figure&gt;
  &lt;picture&gt;
    &lt;source srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/elqsdYqQEefWJbUM2qMO.svg&quot; media=&quot;(min-width: 640px)&quot; width=&quot;800&quot; height=&quot;200&quot; /&gt;
    &lt;img alt=&quot;Good LCP values are 2.5 seconds or less, poor values are greater than 4.0 seconds, and anything in between needs improvement&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/eqprBhZUGfb8WYnumQ9ljAxRrA72/8ZW8LQsagLih1ZZoOmMR.svg&quot; width=&quot;640&quot; /&gt;
  &lt;/picture&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; To learn more about the research and methodology behind this recommendation, see: &lt;a href=&quot;https://web.dev/defining-core-web-vitals-thresholds/&quot;&gt;Defining the Core Web Vitals metrics thresholds&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;what-elements-are-considered&quot;&gt;What elements are considered? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#what-elements-are-considered&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As currently specified in the &lt;a href=&quot;https://wicg.github.io/largest-contentful-paint/&quot; rel=&quot;noopener&quot;&gt;Largest Contentful Paint
API&lt;/a&gt;, the types of elements
considered for Largest Contentful Paint are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt; elements inside an &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; elements with a poster image (the poster image load time is used)&lt;/li&gt;
&lt;li&gt;An element with a background image loaded via the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/url()&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;url()&lt;/code&gt;&lt;/a&gt; function
(as opposed to a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/CSS_Images/Using_CSS_gradients&quot; rel=&quot;noopener&quot;&gt;CSS gradient&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Block-level_elements&quot; rel=&quot;noopener&quot;&gt;Block-level&lt;/a&gt;
elements containing text nodes or other inline-level text elements children.&lt;/li&gt;
&lt;li&gt;The first frame painted for autoplaying &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; elements (as of &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/speed/metrics_changelog/lcp.md&quot; rel=&quot;noopener&quot;&gt;August 2023&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The first frame of an animated image format, such as animated GIFs (as of &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/speed/metrics_changelog/lcp.md&quot; rel=&quot;noopener&quot;&gt;August 2023&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As well as only considering some elements, certain heurisitics are applied to exclude certain elements that are likely to be seen as &amp;quot;non-contentful&amp;quot; to users. For Chromium-based browsers, these include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Elements with an opacity of 0, that are invisible to the user&lt;/li&gt;
&lt;li&gt;Elements that cover the full viewport, that are likely considered as background rather than content&lt;/li&gt;
&lt;li&gt;Placeholder images or other images with a low entropy, that likely do not reflect the true content of the page&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Browsers are likely to continue to improve these heuristics to ensure we match user expectations of what the largest &lt;em&gt;contentful&lt;/em&gt; element is.&lt;/p&gt;
&lt;p&gt;These &amp;quot;contentful&amp;quot; heuristics may differ from those used by &lt;a href=&quot;https://web.dev/fcp/&quot;&gt;First Contentful Paint (FCP)&lt;/a&gt;, which may consider some of these elements, such as placeholder images or full viewport images, even if they are ineligible to be LCP candidates. Despite both using &amp;quot;contentful&amp;quot; in their name, the aim of these metrics is different. FCP measures when &lt;em&gt;any content&lt;/em&gt; is painted to screen and LCP when the &lt;em&gt;main content&lt;/em&gt; is painted so LCP is intented to be more selective.&lt;/p&gt;
&lt;h3 id=&quot;how-is-an-elements-size-determined&quot;&gt;How is an element&#39;s size determined? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#how-is-an-elements-size-determined&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The size of the element reported for Largest Contentful Paint is typically the
size that&#39;s visible to the user within the viewport. If the element extends
outside of the viewport, or if any of the element is clipped or has non-visible
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/overflow&quot; rel=&quot;noopener&quot;&gt;overflow&lt;/a&gt;, those
portions do not count toward the element&#39;s size.&lt;/p&gt;
&lt;p&gt;For image elements that have been resized from their &lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Intrinsic_Size&quot; rel=&quot;noopener&quot;&gt;intrinsic
size&lt;/a&gt;, the
size that gets reported is either the visible size or the intrinsic size,
whichever is smaller. For example, images that are shrunk down to a much
smaller than their intrinsic size will only report the size they&#39;re displayed
at, whereas images that are stretched or expanded to a larger size will only
report their intrinsic sizes.&lt;/p&gt;
&lt;p&gt;For text elements, only the size of their text nodes is considered (the smallest
rectangle that encompasses all text nodes).&lt;/p&gt;
&lt;p&gt;For all elements, any margin, padding, or border applied via CSS is not
considered.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Determining which text nodes belong to which elements can sometimes be tricky, especially for elements whose children includes inline elements and plain text nodes but also block-level elements. The key point is that every text node belongs to (and only to) its closest block-level ancestor element. In &lt;a href=&quot;https://wicg.github.io/element-timing/#set-of-owned-text-nodes&quot;&gt;spec terms&lt;/a&gt;: each text node belongs to the element that generates its &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Containing_block&quot;&gt;containing block&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;when-is-largest-contentful-paint-reported&quot;&gt;When is largest contentful paint reported? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#when-is-largest-contentful-paint-reported&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Web pages often load in stages, and as a result, it&#39;s possible that the largest
element on the page might change.&lt;/p&gt;
&lt;p&gt;To handle this potential for change, the browser dispatches a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceEntry&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;PerformanceEntry&lt;/code&gt;&lt;/a&gt;
of type &lt;code&gt;largest-contentful-paint&lt;/code&gt; identifying the largest contentful element
as soon as the browser has painted the first frame. But then, after rendering
subsequent frames, it will dispatch another
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceEntry&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;PerformanceEntry&lt;/code&gt;&lt;/a&gt;
any time the largest contentful element changes.&lt;/p&gt;
&lt;p&gt;For example, on a page with text and a hero image the browser may initially just
render the text—at which point the browser would dispatch a
&lt;code&gt;largest-contentful-paint&lt;/code&gt; entry whose &lt;code&gt;element&lt;/code&gt; property would likely reference
a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;. Later, once the hero image finishes loading, a second
&lt;code&gt;largest-contentful-paint&lt;/code&gt; entry would be dispatched and its &lt;code&gt;element&lt;/code&gt; property
would reference the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It&#39;s important to note that an element can only be considered the largest
contentful element once it has rendered and is visible to the user. Images that
have not yet loaded are not considered &amp;quot;rendered&amp;quot;. Neither are text nodes using
web fonts during the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/@font-face/font-display#The_font_display_timeline&quot; rel=&quot;noopener&quot;&gt;font block
period&lt;/a&gt;.
In such cases a smaller element may be reported as the largest contentful
element, but as soon as the larger element finishes rendering, it&#39;ll be
reported via another  &lt;code&gt;PerformanceEntry&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;In addition to late-loading images and fonts, a page may add new elements to
the DOM as new content becomes available. If any of these new elements is
larger than the previous largest contentful element, a new &lt;code&gt;PerformanceEntry&lt;/code&gt;
will also be reported.&lt;/p&gt;
&lt;p&gt;If an element that is currently the largest contentful element is removed from
the viewport (or even removed from the DOM), it will remain the largest
contentful element unless a larger element is rendered.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Prior to Chrome 88, removed elements were not considered as largest contentful elements, and removing the current candidate would trigger a new &lt;code&gt;largest-contentful-paint&lt;/code&gt; entry to be dispatched. However, due to popular UI patterns such as image carousels that often removed DOM elements, the metric was updated to more accurately reflect what users experience. See the &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/main/docs/speed/metrics_changelog/2020_11_lcp.md&quot;&gt;CHANGELOG&lt;/a&gt; for more details. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;The browser will stop reporting new entries as soon as the user interacts with
the page (via a tap, scroll, or keypress), as user interaction often changes
what&#39;s visible to the user (which is especially true with scrolling).&lt;/p&gt;
&lt;p&gt;For analysis purposes, you should only report the most recently dispatched
&lt;code&gt;PerformanceEntry&lt;/code&gt; to your analytics service.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Since users can open pages in a background tab, it&#39;s possible that &lt;code&gt;largest-contentful-paint&lt;/code&gt; entries will not be dispatched until the user focuses the tab, which can be much later than when they first loaded it.  Google tools that measure LCP do not report this metric if the page was loaded in the background, as it does not reflect the user-perceived load time. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;load-time-vs-render-time&quot;&gt;Load time vs. render time &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#load-time-vs-render-time&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;For security reasons, the render timestamp of images is not exposed for
cross-origin images that lack the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Timing-Allow-Origin&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Timing-Allow-Origin&lt;/code&gt;&lt;/a&gt;
header. Instead, only their load time is exposed (since this is already exposed
via many other web APIs).&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/lcp/#measure-lcp-in-javascript&quot;&gt;usage example&lt;/a&gt;
below shows how to handle elements whose render time is not available. But,
when possible, it&#39;s always recommended to set the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Timing-Allow-Origin&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Timing-Allow-Origin&lt;/code&gt;&lt;/a&gt;
header, so your metrics will be more accurate.&lt;/p&gt;
&lt;h3 id=&quot;how-are-element-layout-and-size-changes-handled&quot;&gt;How are element layout and size changes handled? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#how-are-element-layout-and-size-changes-handled&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To keep the performance overhead of calculating and dispatching new performance
entries low, changes to an element&#39;s size or position do not generate new LCP
candidates. Only the element&#39;s initial size and position in the viewport is
considered.&lt;/p&gt;
&lt;p&gt;This means images that are initially rendered off-screen and then transition
on-screen may not be reported. It also means elements initially rendered in the
viewport that then get pushed down, out of view will still report their
initial, in-viewport size.&lt;/p&gt;
&lt;h3 id=&quot;examples&quot;&gt;Examples &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#examples&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here are some examples of when the Largest Contentful Paint occurs on a few
popular websites:&lt;/p&gt;
&lt;img alt=&quot;Largest Contentful Paint timeline from cnn.com&quot; decoding=&quot;async&quot; height=&quot;311&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/bsBm8poY1uQbq7mNvVJm.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;img alt=&quot;Largest Contentful Paint timeline from techcrunch.com&quot; decoding=&quot;async&quot; height=&quot;311&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/xAvLL1u2KFRaqoZZiI71.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;In both of the timelines above, the largest element changes as content loads.
In the first example, new content is added to the DOM and that changes what
element is the largest. In the second example, the layout changes and content
that was previously the largest is removed from the viewport.&lt;/p&gt;
&lt;p&gt;While it&#39;s often the case that late-loading content is larger than content
already on the page, that&#39;s not necessarily the case. The next two examples
show the Largest Contentful Paint occurring before the page fully loads.&lt;/p&gt;
&lt;img alt=&quot;Largest Contentful Paint timeline from instagram.com&quot; decoding=&quot;async&quot; height=&quot;311&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/uJAGswhXK3bE6Vs4I5bP.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;img alt=&quot;Largest Contentful Paint timeline from google.com&quot; decoding=&quot;async&quot; height=&quot;311&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/e0O2woQjZJ92aYlPOJzT.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;In the first example, the Instagram logo is loaded relatively early and it
remains the largest element even as other content is progressively shown. In
the Google search results page example, the largest element is a paragraph of
text that is displayed before any of the images or logo finish loading. Since
all the individual images are smaller than this paragraph, it remains the
largest element throughout the load process.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; In the first frame of the Instagram timeline, you may notice the camera logo does not have a green box around it. That&#39;s because it&#39;s an &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element, and &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; elements are not currently considered LCP candidates. The first LCP candidate is the text in the second frame. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;how-to-measure-lcp&quot;&gt;How to measure LCP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#how-to-measure-lcp&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;LCP can be measured &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;in the lab&lt;/a&gt;
or &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;in the field&lt;/a&gt;, and it&#39;s
available in the following tools:&lt;/p&gt;
&lt;h3 id=&quot;field-tools&quot;&gt;Field tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#field-tools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience
Report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed Insights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/webmasters/answer/9205520&quot; rel=&quot;noopener&quot;&gt;Search Console (Core Web Vitals
report)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;lab-tools&quot;&gt;Lab tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#lab-tools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/&quot; rel=&quot;noopener&quot;&gt;Chrome DevTools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed Insights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webpagetest.org/&quot; rel=&quot;noopener&quot;&gt;WebPageTest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;measure-lcp-in-javascript&quot;&gt;Measure LCP in JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#measure-lcp-in-javascript&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To measure LCP in JavaScript, you can use the &lt;a href=&quot;https://wicg.github.io/largest-contentful-paint/&quot; rel=&quot;noopener&quot;&gt;Largest Contentful Paint
API&lt;/a&gt;. The following example
shows how to create a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;PerformanceObserver&lt;/code&gt;&lt;/a&gt;
that listens for &lt;code&gt;largest-contentful-paint&lt;/code&gt; entries and logs them to the
console.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entryList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;LCP candidate:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;largest-contentful-paint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt;  This code shows how to log &lt;code&gt;largest-contentful-paint&lt;/code&gt; entries to the console, but measuring LCP in JavaScript is more complicated. See below for details:  &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;In the above example, each logged &lt;code&gt;largest-contentful-paint&lt;/code&gt; entry represents
the current LCP candidate. In general, the &lt;code&gt;startTime&lt;/code&gt; value of the last entry
emitted is the LCP value—however, that is not always the case. Not all
&lt;code&gt;largest-contentful-paint&lt;/code&gt; entries are valid for measuring LCP.&lt;/p&gt;
&lt;p&gt;The following section lists the differences between what the API reports and how
the metric is calculated.&lt;/p&gt;
&lt;h4 id=&quot;differences-between-the-metric-and-the-api&quot;&gt;Differences between the metric and the API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#differences-between-the-metric-and-the-api&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;The API will dispatch &lt;code&gt;largest-contentful-paint&lt;/code&gt; entries for pages loaded in a
background tab, but those pages should be ignored when calculating LCP.&lt;/li&gt;
&lt;li&gt;The API will continue to dispatch &lt;code&gt;largest-contentful-paint&lt;/code&gt; entries after a
page has been backgrounded, but those entries should be ignored when
calculating LCP (elements may only be considered if the page was in the
foreground the entire time).&lt;/li&gt;
&lt;li&gt;The API does not report &lt;code&gt;largest-contentful-paint&lt;/code&gt; entries when the page is
restored from the &lt;a href=&quot;https://web.dev/bfcache/#impact-on-core-web-vitals&quot;&gt;back/forward cache&lt;/a&gt;,
but LCP should be measured in these cases since users experience them as
distinct page visits.&lt;/li&gt;
&lt;li&gt;The API does not consider elements within iframes but the metric does as they
are part of the user experience of the page. In pages with an LCP within an
iframe—for example a poster image on an embedded video—this will
&lt;a href=&quot;https://web.dev/crux-and-rum-differences/#iframes&quot;&gt;show as a difference between CrUX and RUM&lt;/a&gt;.
To properly measure LCP you should consider them. Sub-frames can use the API
to report their &lt;code&gt;largest-contentful-paint&lt;/code&gt; entries to the parent frame for
aggregation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rather than memorizing all these subtle differences, developers can use the
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt; to
measure LCP, which handles these differences for you (where possible):&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onLCP&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;web-vitals&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Measure and log LCP as soon as it&#39;s available.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onLCP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can refer to &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/blob/main/src/onLCP.ts&quot; rel=&quot;noopener&quot;&gt;the source code for
&lt;code&gt;onLCP()&lt;/code&gt;&lt;/a&gt;
for a complete example of how to measure LCP in JavaScript.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; In some cases (such as cross-origin iframes) it&#39;s not possible to measure LCP in JavaScript. See the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#limitations&quot;&gt;limitations&lt;/a&gt; section of the &lt;code&gt;web-vitals&lt;/code&gt; library for details. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;what-if-the-largest-element-isnt-the-most-important&quot;&gt;What if the largest element isn&#39;t the most important? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#what-if-the-largest-element-isnt-the-most-important&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In some cases the most important element (or elements) on the page is not the
same as the largest element, and developers may be more interested in measuring
the render times of these other elements instead. This is possible using the
&lt;a href=&quot;https://wicg.github.io/element-timing/&quot; rel=&quot;noopener&quot;&gt;Element Timing API&lt;/a&gt;, as described in
the article on &lt;a href=&quot;https://web.dev/custom-metrics/#element-timing-api&quot;&gt;custom metrics&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-to-improve-lcp&quot;&gt;How to improve LCP &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#how-to-improve-lcp&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A full guide on &lt;a href=&quot;https://web.dev/optimize-lcp/&quot;&gt;optimizing LCP&lt;/a&gt; is available to guide you through the process of identifying LCP timings in the field and using lab data to drill down and optimize them.&lt;/p&gt;
&lt;h2 id=&quot;additional-resources&quot;&gt;Additional resources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#additional-resources&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/ctavZT87syI&quot; rel=&quot;noopener&quot;&gt;Lessons learned from performance monitoring in Chrome&lt;/a&gt; by &lt;a href=&quot;https://anniesullie.com/&quot; rel=&quot;noopener&quot;&gt;Annie Sullivan&lt;/a&gt; at &lt;a href=&quot;https://perfnow.nl/&quot; rel=&quot;noopener&quot;&gt;performance.now()&lt;/a&gt; (2019)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changelog&quot;&gt;CHANGELOG &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/lcp/#changelog&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Occasionally, bugs are discovered in the APIs used to measure metrics, and sometimes in the definitions of the metrics themselves. As a result, changes must sometimes be made, and these changes can show up as improvements or regressions in your internal reports and dashboards.&lt;/p&gt;
&lt;p&gt;To help you manage this, all changes to either the implementation or definition of these metrics will be surfaced in this &lt;a href=&quot;http://bit.ly/chrome-speed-metrics-changelog&quot; rel=&quot;noopener&quot;&gt;CHANGELOG&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have feedback for these metrics, you can provide it in the &lt;a href=&quot;https://groups.google.com/g/web-vitals-feedback&quot; rel=&quot;noopener&quot;&gt;web-vitals-feedback Google group&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author><author>
      <name>Barry Pollard</name>
    </author>
  </entry>
  
  <entry>
    <title>Cumulative Layout Shift (CLS)</title>
    <link href="https://web.dev/cls/"/>
    <updated>2019-06-11T00:00:00Z</updated>
    <id>https://web.dev/cls/</id>
    <content type="html" mode="escaped">&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 77, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      77
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
79
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/LayoutShift#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;aside class=&quot;aside flow color-secondary-box-text bg-secondary-box-bg&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Highlighter pen&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M10.22 9.49l-5.91 6c-.77.8-.7 2.05.08 2.85L.77 22h5.68l.74-.75c.78.81 1.95.86 2.73.05l5.96-6.05-5.66-5.76zm12.46-4l-2.82-2.87c-.78-.8-2.07-.84-2.84-.04l-5.75 5.85 5.66 5.75 5.69-5.78c.77-.81.83-2.11.06-2.91z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Key Term&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Cumulative Layout Shift (CLS) is a stable Core Web Vital metric. It is an important, user-centric metric for measuring &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#types-of-metrics&quot;&gt;visual stability&lt;/a&gt; because it helps quantify how often users experience unexpected layout shifts—a low CLS helps ensure that the page is &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#questions&quot;&gt;delightful&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Have you ever been reading an article online when something suddenly changes on
the page? Without warning, the text moves, and you&#39;ve lost your place. Or even
worse: you&#39;re about to tap a link or a button, but in the instant before your
finger lands—BOOM—the link moves, and you end up clicking something
else!&lt;/p&gt;
&lt;p&gt;Most of the time these kinds of experiences are just annoying, but in some
cases, they can cause real damage.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; controls=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; poster=&quot;https://storage.googleapis.com/web-dev-assets/layout-instability-api/layout-instability-poster.png&quot; width=&quot;658&quot; height=&quot;510&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/layout-instability-api/layout-instability2.webm&quot; type=&quot;video/webm; codecs=vp8&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/layout-instability-api/layout-instability2.mp4&quot; type=&quot;video/mp4; codecs=h264&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    A screencast illustrating how layout instability can negatively affect
    users.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Unexpected movement of page content usually happens because resources are loaded
asynchronously or DOM elements get dynamically added to the page above existing
content. The culprit might be an image or video with unknown dimensions, a font
that renders larger or smaller than its fallback, or a third-party ad or widget
that dynamically resizes itself.&lt;/p&gt;
&lt;p&gt;What makes this issue even more problematic is that how a site functions in
development is often quite different from how users experience it. Personalized
or third-party content often doesn&#39;t behave the same in development as it does
in production, test images are often already in the developer&#39;s browser cache,
and API calls that run locally are often so fast that the delay isn&#39;t
noticeable.&lt;/p&gt;
&lt;p&gt;The Cumulative Layout Shift (CLS) metric helps you address this problem by
measuring how often it&#39;s occurring for real users.&lt;/p&gt;
&lt;h2 id=&quot;what-is-cls&quot;&gt;What is CLS? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#what-is-cls&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CLS is a measure of the largest burst of &lt;em&gt;layout shift scores&lt;/em&gt; for every
&lt;a href=&quot;https://web.dev/cls/#expected-vs-unexpected-layout-shifts&quot;&gt;unexpected&lt;/a&gt; layout shift that
occurs during the entire lifespan of a page.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;layout shift&lt;/em&gt; occurs any time a visible element changes its position from one
rendered frame to the next. (See below for details on how individual &lt;a href=&quot;https://web.dev/cls/#layout-shift-score&quot;&gt;layout
shift scores&lt;/a&gt; are calculated.)&lt;/p&gt;
&lt;p&gt;A burst of layout shifts, known as a &lt;a href=&quot;https://web.dev/evolving-cls/#why-a-session-window&quot;&gt;&lt;em&gt;session
window&lt;/em&gt;&lt;/a&gt;, is when one or more individual
layout shifts occur in rapid succession with less than 1-second in between each
shift and a maximum of 5 seconds for the total window duration.&lt;/p&gt;
&lt;p&gt;The largest burst is the session window with the maximum cumulative score of all
layout shifts within that window.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; width=&quot;658&quot; height=&quot;452&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/better-layout-shift-metric/session-window.webm&quot; type=&quot;video/webm&quot; /&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/better-layout-shift-metric/session-window.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Example of session windows. Blue bars represent the scores of each individual layout shift.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Previously CLS measured the sum total of &lt;em&gt;all individual layout shift scores&lt;/em&gt; that occurred during the entire lifespan of the page. To see which tools still provide the ability to benchmark against the original implementation, check out &lt;a href=&quot;https://web.dev/cls-web-tooling&quot;&gt;Evolving Cumulative Layout Shift in web tooling&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;what-is-a-good-cls-score&quot;&gt;What is a good CLS score? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#what-is-a-good-cls-score&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To provide a good user experience, sites should strive to have a CLS score of
&lt;strong&gt;0.1&lt;/strong&gt; or less. To ensure you&#39;re hitting this target for most of your users, a
good threshold to measure is the &lt;strong&gt;75th percentile&lt;/strong&gt; of page loads, segmented
across mobile and desktop devices.&lt;/p&gt;
&lt;figure&gt;
  &lt;picture&gt;
    &lt;source srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9mWVASbWDLzdBUpVcjE1.svg&quot; media=&quot;(min-width: 640px)&quot; width=&quot;800&quot; height=&quot;200&quot; /&gt;
    &lt;img alt=&quot;Good CLS values are 0.1 or less, poor values are greater than 0.25, and anything in between needs improvement&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uqclEgIlTHhwIgNTXN3Y.svg&quot; width=&quot;640&quot; /&gt;
  &lt;/picture&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; To learn more about the research and methodology behind this recommendation, see: &lt;a href=&quot;https://web.dev/defining-core-web-vitals-thresholds/&quot;&gt;Defining the Core Web Vitals metrics thresholds&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;layout-shifts-in-detail&quot;&gt;Layout shifts in detail &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#layout-shifts-in-detail&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Layout shifts are defined by the &lt;a href=&quot;https://github.com/WICG/layout-instability&quot; rel=&quot;noopener&quot;&gt;Layout Instability
API&lt;/a&gt;, which reports &lt;code&gt;layout-shift&lt;/code&gt;
entries any time an element that is visible within the viewport changes its
start position (for example, its top and left position in the default &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/writing-mode&quot; rel=&quot;noopener&quot;&gt;writing
mode&lt;/a&gt;) between
two frames. Such elements are considered &lt;em&gt;unstable elements&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Note that layout shifts only occur when existing elements change their start
position. If a new element is added to the DOM or an existing element changes
size, it doesn&#39;t count as a layout shift—as long as the change doesn&#39;t
cause other visible elements to change their start position.&lt;/p&gt;
&lt;h3 id=&quot;layout-shift-score&quot;&gt;Layout shift score &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#layout-shift-score&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To calculate the &lt;em&gt;layout shift score&lt;/em&gt;, the browser looks at the viewport size
and the movement of &lt;em&gt;unstable elements&lt;/em&gt; in the viewport between two rendered
frames. The layout shift score is a product of two measures of that movement:
the &lt;em&gt;impact fraction&lt;/em&gt; and the &lt;em&gt;distance fraction&lt;/em&gt; (both defined below).&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;layout shift score = impact fraction * distance fraction&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;impact-fraction&quot;&gt;Impact fraction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#impact-fraction&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/WICG/layout-instability#Impact-Fraction&quot; rel=&quot;noopener&quot;&gt;impact
fraction&lt;/a&gt; measures
how &lt;em&gt;unstable elements&lt;/em&gt; impact the viewport area between two frames.&lt;/p&gt;
&lt;p&gt;The union of the visible areas of all &lt;em&gt;unstable elements&lt;/em&gt; for the previous frame
&lt;em&gt;and&lt;/em&gt; the current frame—as a fraction of the total area of the
viewport—is the &lt;em&gt;impact fraction&lt;/em&gt; for the current frame.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&quot;&gt;&lt;img alt=&quot;Impact fraction example with one _unstable element_&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BbpE9rFQbF8aU6iXN1U6.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the image above there&#39;s an element that takes up half of the viewport in one
frame. Then, in the next frame, the element shifts down by 25% of the viewport
height. The red, dotted rectangle indicates the union of the element&#39;s visible
area in both frames, which, in this case, is 75% of the total viewport, so its
&lt;em&gt;impact fraction&lt;/em&gt; is &lt;code&gt;0.75&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;distance-fraction&quot;&gt;Distance fraction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#distance-fraction&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The other part of the layout shift score equation measures the distance that
unstable elements have moved, relative to the viewport. The &lt;em&gt;distance fraction&lt;/em&gt;
is the greatest distance any &lt;em&gt;unstable element&lt;/em&gt; has moved in the frame (either
horizontally or vertically) divided by the viewport&#39;s largest dimension (width
or height, whichever is greater).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&quot;&gt;&lt;img alt=&quot;Distance fraction example with one _unstable element_&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASnfpVs2n9winu6mmzdk.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the example above, the largest viewport dimension is the height, and the
unstable element has moved by 25% of the viewport height, which makes the
&lt;em&gt;distance fraction&lt;/em&gt; 0.25.&lt;/p&gt;
&lt;p&gt;So, in this example the &lt;em&gt;impact fraction&lt;/em&gt; is &lt;code&gt;0.75&lt;/code&gt; and the &lt;em&gt;distance fraction&lt;/em&gt;
is &lt;code&gt;0.25&lt;/code&gt;, so the &lt;em&gt;layout shift score&lt;/em&gt; is &lt;code&gt;0.75 * 0.25 = 0.1875&lt;/code&gt;.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Initially, the layout shift score was calculated based only on &lt;em&gt;impact fraction&lt;/em&gt;. The &lt;em&gt;distance fraction&lt;/em&gt; was introduced to avoid overly penalizing cases where large elements shift by a small amount. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;The next example illustrates how adding content to an existing element affects
the layout shift score:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&quot;&gt;&lt;img alt=&quot;Layout shift example with multiple stable and _unstable elements_&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xhN81DazXCs8ZawoCj0T.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &amp;quot;Click Me!&amp;quot; button is appended to the bottom of the gray box with black
text, which pushes the green box with white text down (and partially out of the
viewport).&lt;/p&gt;
&lt;p&gt;In this example, the gray box changes size, but its start position does not
change so it&#39;s not an &lt;em&gt;unstable element&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The &amp;quot;Click Me!&amp;quot; button was not previously in the DOM, so its start position
doesn&#39;t change either.&lt;/p&gt;
&lt;p&gt;The start position of the green box, however, does change, but since it&#39;s been
moved partially out of the viewport, the invisible area is not considered when
calculating the &lt;em&gt;impact fraction&lt;/em&gt;. The union of the visible areas for the green
box in both frames (illustrated by the red, dotted rectangle) is the same as the
area of the green box in the first frame—50% of the viewport. The &lt;em&gt;impact
fraction&lt;/em&gt; is &lt;code&gt;0.5&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;distance fraction&lt;/em&gt; is illustrated with the purple arrow. The green box has
moved down by about 14% of the viewport so the &lt;em&gt;distance fraction&lt;/em&gt; is &lt;code&gt;0.14&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The layout shift score is &lt;code&gt;0.5 x 0.14 = 0.07&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This last example illustrates multiple &lt;em&gt;unstable elements&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&quot;&gt;&lt;img alt=&quot;Layout shift example with stable and _unstable elements_ and viewport clipping&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/W3z1f5ZkBJSgL1V1IfloTIctbIF3/J8AWG72qYlmbAHxjxuLg.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the first frame above there are four results of an API request for animals,
sorted in alphabetical order. In the second frame, more results are added to the
sorted list.&lt;/p&gt;
&lt;p&gt;The first item in the list (&amp;quot;Cat&amp;quot;) does not change its start position between
frames, so it&#39;s stable. Similarly, the new items added to the list were not
previously in the DOM, so their start positions don&#39;t change either. But the
items labelled &amp;quot;Dog&amp;quot;, &amp;quot;Horse&amp;quot;, and &amp;quot;Zebra&amp;quot; all shift their start positions,
making them &lt;em&gt;unstable elements&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Again, the red, dotted rectangles represent the union of these three &lt;em&gt;unstable
elements&lt;/em&gt;&#39; before and after areas, which in this case is around 60% of the
viewport&#39;s area (&lt;em&gt;impact fraction&lt;/em&gt; of &lt;code&gt;0.60&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The arrows represent the distances that &lt;em&gt;unstable elements&lt;/em&gt; have moved from
their starting positions. The &amp;quot;Zebra&amp;quot; element, represented by the blue arrow,
has moved the most, by about 30% of the viewport height. That makes the
&lt;em&gt;distance fraction&lt;/em&gt; in this example &lt;code&gt;0.3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The layout shift score is &lt;code&gt;0.60 x 0.3 = 0.18&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;expected-vs-unexpected-layout-shifts&quot;&gt;Expected vs. unexpected layout shifts &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#expected-vs-unexpected-layout-shifts&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Not all layout shifts are bad. In fact, many dynamic web applications frequently
change the start position of elements on the page.&lt;/p&gt;
&lt;h4 id=&quot;user-initiated-layout-shifts&quot;&gt;User-initiated layout shifts &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#user-initiated-layout-shifts&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A layout shift is only bad if the user isn&#39;t expecting it. On the other hand,
layout shifts that occur in response to user interactions (clicking a link,
pressing a button, typing in a search box and similar) are generally fine, as
long as the shift occurs close enough to the interaction that the relationship
is clear to the user.&lt;/p&gt;
&lt;p&gt;For example, if a user interaction triggers a network request that may take a
while to complete, it&#39;s best to create some space right away and show a loading
indicator to avoid an unpleasant layout shift when the request completes. If the
user doesn&#39;t realize something is loading, or doesn&#39;t have a sense of when the
resource will be ready, they may try to click something else while
waiting—something that could move out from under them.&lt;/p&gt;
&lt;p&gt;Layout shifts that occur within 500 milliseconds of user input will have the
&lt;a href=&quot;https://wicg.github.io/layout-instability/#dom-layoutshift-hadrecentinput&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;hadRecentInput&lt;/code&gt;&lt;/a&gt;
flag set, so they can be excluded from calculations.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The &lt;code&gt;hadRecentInput&lt;/code&gt; flag will only be true for discrete input events like tap, click, or keypress. Continuous interactions such as scrolls, drags, or pinch and zoom gestures are not considered &amp;quot;recent input&amp;quot;. See the &lt;a href=&quot;https://github.com/WICG/layout-instability#recent-input-exclusion&quot;&gt;Layout Instability Spec&lt;/a&gt; for more details. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;animations-and-transitions&quot;&gt;Animations and transitions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#animations-and-transitions&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Animations and transitions, when done well, are a great way to update content on
the page without surprising the user. Content that shifts abruptly and
unexpectedly on the page almost always creates a bad user experience. But
content that moves gradually and naturally from one position to the next can
often help the user better understand what&#39;s going on, and guide them between
state changes.&lt;/p&gt;
&lt;p&gt;Be sure to respect &lt;a href=&quot;https://web.dev/prefers-reduced-motion/&quot;&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/a&gt; browser settings, as some site visitors
can experience ill effects or attention issues from animation.&lt;/p&gt;
&lt;p&gt;CSS &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/transform&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;transform&lt;/code&gt;&lt;/a&gt;
property allows you to animate elements without triggering layout shifts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instead of changing the &lt;code&gt;height&lt;/code&gt; and &lt;code&gt;width&lt;/code&gt; properties, use &lt;code&gt;transform: scale()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To move elements around, avoid changing the &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;right&lt;/code&gt;, &lt;code&gt;bottom&lt;/code&gt;, or
&lt;code&gt;left&lt;/code&gt; properties and use &lt;code&gt;transform: translate()&lt;/code&gt; instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-to-measure-cls&quot;&gt;How to measure CLS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#how-to-measure-cls&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;CLS can be measured &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-lab&quot;&gt;in the lab&lt;/a&gt;
or &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#in-the-field&quot;&gt;in the field&lt;/a&gt;, and it&#39;s
available in the following tools:&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Lab tools typically load pages in a synthetic environment and are thus only able to measure layout shifts that occur during page load. As a result, CLS values reported by lab tools for a given page may be less than what real users experience in the field. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;field-tools&quot;&gt;Field tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#field-tools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/crux/&quot; rel=&quot;noopener&quot;&gt;Chrome User Experience
Report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed Insights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.google.com/webmasters/answer/9205520&quot; rel=&quot;noopener&quot;&gt;Search Console (Core Web Vitals
report)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;lab-tools&quot;&gt;Lab tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#lab-tools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/&quot; rel=&quot;noopener&quot;&gt;Chrome DevTools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pagespeed.web.dev/&quot; rel=&quot;noopener&quot;&gt;PageSpeed Insights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webpagetest.org/&quot; rel=&quot;noopener&quot;&gt;WebPageTest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;measure-layout-shifts-in-javascript&quot;&gt;Measure layout shifts in JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#measure-layout-shifts-in-javascript&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To measure layout shifts in JavaScript, you use the &lt;a href=&quot;https://github.com/WICG/layout-instability&quot; rel=&quot;noopener&quot;&gt;Layout Instability API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following example shows how to create a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PerformanceObserver&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;PerformanceObserver&lt;/code&gt;&lt;/a&gt; to log &lt;code&gt;layout-shift&lt;/code&gt; entries to the console:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PerformanceObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;entryList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entryList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Layout shift:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;layout-shift&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;buffered&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;measure-cls-in-javascript&quot;&gt;Measure CLS in JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#measure-cls-in-javascript&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To measure CLS in JavaScript, you need to group these unexpected &lt;code&gt;layout-shift&lt;/code&gt; entries into sessions, and calculate the maximum session value. You can refer to the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals/blob/main/src/onCLS.ts&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;web vitals&lt;/code&gt; JavaScript library source code&lt;/a&gt; which contains a reference implementation on how CLS is calculated.&lt;/p&gt;
&lt;p&gt;In most cases, the current CLS value at the time the page is being unloaded is the final CLS value for that page, but there are a few important exceptions as noted in the next section. The &lt;code&gt;web vitals&lt;/code&gt; JavaScript library accounts for these as much as possible, within the limitations of the Web APIs.&lt;/p&gt;
&lt;h4 id=&quot;differences-between-the-metric-and-the-api&quot;&gt;Differences between the metric and the API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#differences-between-the-metric-and-the-api&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;If a page is loaded in the background, or if it&#39;s backgrounded prior to the
browser painting any content, then it should not report any CLS value.&lt;/li&gt;
&lt;li&gt;If a page is restored from the &lt;a href=&quot;https://web.dev/bfcache/#impact-on-core-web-vitals&quot;&gt;back/forward
cache&lt;/a&gt;, its CLS value should be reset to
zero since users experience this as a distinct page visit.&lt;/li&gt;
&lt;li&gt;The API does not report &lt;code&gt;layout-shift&lt;/code&gt; entries for shifts that occur within
iframes but the metric does as they are part of the user experience of the
page. This can
&lt;a href=&quot;https://web.dev/crux-and-rum-differences/#iframes&quot;&gt;show as a difference between CrUX and RUM&lt;/a&gt;.
To properly measure CLS you should consider them. Sub-frames can
use the API to report their &lt;code&gt;layout-shift&lt;/code&gt; entries to the parent frame for
&lt;a href=&quot;https://github.com/WICG/layout-instability#cumulative-scores&quot; rel=&quot;noopener&quot;&gt;aggregation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to these exceptions, CLS has some added complexity due to the fact
that it measures the entire lifespan of a page:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Users might keep a tab open for a &lt;em&gt;very&lt;/em&gt; long time—days, weeks, months.
In fact, a user might never close a tab.&lt;/li&gt;
&lt;li&gt;On mobile operating systems, browsers typically do not run page unload
callbacks for background tabs, making it difficult to report the &amp;quot;final&amp;quot;
value.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To handle such cases, CLS should be reported any time a page is
background—in addition to any time it&#39;s unloaded (the &lt;a href=&quot;https://developer.chrome.com/blog/page-lifecycle-api/#event-visibilitychange&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;visibilitychange&lt;/code&gt;
event&lt;/a&gt;
covers both of these scenarios). And analytics systems receiving this data will
then need to calculate the final CLS value on the backend.&lt;/p&gt;
&lt;p&gt;Rather than memorizing and grappling with all of these cases yourself, developers can use the
&lt;a href=&quot;https://github.com/GoogleChrome/web-vitals&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;web-vitals&lt;/code&gt; JavaScript library&lt;/a&gt; to
measure CLS, which accounts for everything mentioned above:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;onCLS&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;web-vitals&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Measure and log CLS in all situations&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// where it needs to be reported.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;onCLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; In some cases (such as cross-origin iframes) it&#39;s not possible to measure CLS in JavaScript. See the &lt;a href=&quot;https://github.com/GoogleChrome/web-vitals#limitations&quot;&gt;limitations&lt;/a&gt; section of the &lt;code&gt;web-vitals&lt;/code&gt; library for details. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;how-to-improve-cls&quot;&gt;How to improve CLS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#how-to-improve-cls&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A full guide on &lt;a href=&quot;https://web.dev/optimize-cls/&quot;&gt;optimizing CLS&lt;/a&gt; is available to guide you through the process of identifying layout shifts in the field and using lab data to drill down and optimize them.&lt;/p&gt;
&lt;h2 id=&quot;additional-resources&quot;&gt;Additional resources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#additional-resources&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Google Publisher Tag&#39;s guidance on
&lt;a href=&quot;https://developers.google.com/doubleclick-gpt/guides/minimize-layout-shift&quot; rel=&quot;noopener&quot;&gt;minimizing layout shift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/zIJuY-JCjqw&quot; rel=&quot;noopener&quot;&gt;Understanding Cumulative Layout Shift&lt;/a&gt; by
&lt;a href=&quot;https://anniesullie.com/&quot; rel=&quot;noopener&quot;&gt;Annie Sullivan&lt;/a&gt; and &lt;a href=&quot;https://kobes.ca/&quot; rel=&quot;noopener&quot;&gt;Steve
Kobes&lt;/a&gt; at &lt;a href=&quot;https://perfmattersconf.com/&quot; rel=&quot;noopener&quot;&gt;#PerfMatters&lt;/a&gt;
(2020)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changelog&quot;&gt;CHANGELOG &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/cls/#changelog&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Occasionally, bugs are discovered in the APIs used to measure metrics, and sometimes in the definitions of the metrics themselves. As a result, changes must sometimes be made, and these changes can show up as improvements or regressions in your internal reports and dashboards.&lt;/p&gt;
&lt;p&gt;To help you manage this, all changes to either the implementation or definition of these metrics will be surfaced in this &lt;a href=&quot;http://bit.ly/chrome-speed-metrics-changelog&quot; rel=&quot;noopener&quot;&gt;CHANGELOG&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have feedback for these metrics, you can provide it in the &lt;a href=&quot;https://groups.google.com/g/web-vitals-feedback&quot; rel=&quot;noopener&quot;&gt;web-vitals-feedback Google group&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author><author>
      <name>Milica Mihajlija</name>
    </author>
  </entry>
  
  <entry>
    <title>Best Practices for Using IndexedDB</title>
    <link href="https://web.dev/indexeddb-best-practices/"/>
    <updated>2017-06-08T00:00:00Z</updated>
    <id>https://web.dev/indexeddb-best-practices/</id>
    <content type="html" mode="escaped">&lt;p&gt;When a user first loads a website or application, there&#39;s often a fair amount of work involved in
constructing the initial application state that&#39;s used to render the UI. For example, sometimes the
app needs to authenticate the user client-side and then make several API requests before it has all
the data it needs to display on the page.&lt;/p&gt;
&lt;p&gt;Storing the application state in
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IndexedDB_API&quot; rel=&quot;noopener&quot;&gt;IndexedDB&lt;/a&gt; can be a great way to speed up
the load time for repeat visits. The app can then sync up with any API services in the background
and update the UI with new data lazily, employing a
&lt;a href=&quot;https://www.mnot.net/blog/2007/12/12/stale&quot; rel=&quot;noopener&quot;&gt;stale-while- revalidate&lt;/a&gt; strategy.&lt;/p&gt;
&lt;p&gt;Another good use for IndexedDB is to store user-generated content, either as a temporary store
before it is uploaded to the server or as a client-side cache of remote data - or, of course, both.&lt;/p&gt;
&lt;p&gt;However, when using IndexedDB there are many important things to consider that may not be
immediately obvious to developers new to the APIs. This article answers common questions and
discusses some of the most important things to keep in mind when persisting data in IndexedDB.&lt;/p&gt;
&lt;h2 id=&quot;keeping-your-app-predictable&quot;&gt;Keeping your app predictable &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/indexeddb-best-practices/#keeping-your-app-predictable&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A lot of the complexities around IndexedDB stem from the fact that there are so many factors you
(the developer) have no control over. This section explores many of the issues you must keep in mind
when working with IndexedDB.&lt;/p&gt;
&lt;h3 id=&quot;not-everything-can-be-stored-in-indexeddb-on-all-platforms&quot;&gt;Not everything can be stored in IndexedDB on all platforms &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/indexeddb-best-practices/#not-everything-can-be-stored-in-indexeddb-on-all-platforms&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you are storing large, user-generated files such as images or videos, then you may try to store
them as &lt;code&gt;File&lt;/code&gt; or &lt;code&gt;Blob&lt;/code&gt; objects. This will work on some platforms but fail on others. Safari on
iOS, in particular, cannot store &lt;code&gt;Blob&lt;/code&gt;s in IndexedDB.&lt;/p&gt;
&lt;p&gt;Luckily it is not too difficult to convert a &lt;code&gt;Blob&lt;/code&gt; into an &lt;code&gt;ArrayBuffer&lt;/code&gt;, and vice versa. Storing
&lt;code&gt;ArrayBuffer&lt;/code&gt;s in IndexedDB is very well supported.&lt;/p&gt;
&lt;p&gt;Remember, however, that a &lt;code&gt;Blob&lt;/code&gt; has a MIME type while an &lt;code&gt;ArrayBuffer&lt;/code&gt; does not. You will need to
store the type alongside the buffer in order to do the conversion correctly.&lt;/p&gt;
&lt;p&gt;To convert an &lt;code&gt;ArrayBuffer&lt;/code&gt; to a &lt;code&gt;Blob&lt;/code&gt; you simply use the &lt;code&gt;Blob&lt;/code&gt; constructor.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;arrayBufferToBlob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; type &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The other direction is slightly more involved, and is an asynchronous process. You can use a
&lt;code&gt;FileReader&lt;/code&gt; object to read the blob as an &lt;code&gt;ArrayBuffer&lt;/code&gt;. When the reading is finished a &lt;code&gt;loadend&lt;/code&gt;
event is triggered on the reader. You can wrap this process in a &lt;code&gt;Promise&lt;/code&gt; like so:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blobToArrayBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FileReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;loadend&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;error&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readAsArrayBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;writing-to-storage-may-fail&quot;&gt;Writing to storage may fail &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/indexeddb-best-practices/#writing-to-storage-may-fail&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Errors when writing to IndexedDB can happen for a variety of reasons, and in some cases these
reasons are outside of your control as a developer. For example, some browsers currently don&#39;t allow
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IndexedDB_API#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;writing to IndexedDB when in private browsing mode&lt;/a&gt;.
There&#39;s also the possibility that a user is on a device that&#39;s almost out of disk space, and the
browser will restrict you from storing anything at all.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; New &lt;a href=&quot;https://storage.spec.whatwg.org/&quot;&gt;storage APIs&lt;/a&gt; are currently being developed that will allow developers to &lt;a href=&quot;https://storage.spec.whatwg.org/#usage-and-quota&quot;&gt;get an estimate&lt;/a&gt; of how much storage space is available prior to writing as well as request larger storage quota or even &lt;a href=&quot;https://storage.spec.whatwg.org/#persistence&quot;&gt;persistent storage&lt;/a&gt;, meaning the user can opt-in to preserving data from some sites even when performing standard &lt;a href=&quot;https://support.google.com/accounts/answer/32050&quot;&gt;clear cache/cookies&lt;/a&gt; operations. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Because of this, it&#39;s critically important that you always implement proper error handling in your
IndexedDB code. This also means it&#39;s generally a good idea to keep application state in memory (in
addition to storing it), so the UI doesn&#39;t break when running in private browsing mode or when
storage space isn&#39;t available (even if some of the other app features that require storage won&#39;t
work).&lt;/p&gt;
&lt;p&gt;You can catch errors in IndexedDB operations by adding an event handler for the &lt;code&gt;error&lt;/code&gt; event
whenever you create an &lt;code&gt;IDBDatabase&lt;/code&gt;, &lt;code&gt;IDBTransaction&lt;/code&gt; or &lt;code&gt;IDBRequest&lt;/code&gt; object.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;example-db&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;error&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Request error:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;stored-data-may-have-been-modified-or-deleted-by-the-user&quot;&gt;Stored data may have been modified or deleted by the user &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/indexeddb-best-practices/#stored-data-may-have-been-modified-or-deleted-by-the-user&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unlike server-side databases where you can restrict unauthorized access, client-side databases are
accessible to browser extensions and developer tools, and they can be cleared by the user.&lt;/p&gt;
&lt;p&gt;While it may be uncommon for users to modify their locally stored data, it&#39;s quite common for users
to clear it. It&#39;s important that your application can handle both of these cases without erroring.&lt;/p&gt;
&lt;h3 id=&quot;stored-data-may-be-out-of-date&quot;&gt;Stored data may be out of date &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/indexeddb-best-practices/#stored-data-may-be-out-of-date&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Similar to the previous section, even if the user hasn&#39;t modified the data themselves, it&#39;s also
possible that the data they have in storage was written by an old version of your code, possibly a
version with bugs in it.&lt;/p&gt;
&lt;p&gt;IndexedDB has built-in support for schema versions and upgrading via its
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IDBOpenDBRequest/onupgradeneeded&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;IDBOpenDBRequest.onupgradeneeded()&lt;/code&gt;&lt;/a&gt;
method; however, you still need to write your upgrade code in such a way that it can handle the user
coming from a previous version (including a version with a bug).&lt;/p&gt;
&lt;p&gt;Unit tests can be very helpful here, as it&#39;s often not feasible to manually test all possible
upgrade paths and cases.&lt;/p&gt;
&lt;h2 id=&quot;keeping-your-app-performant&quot;&gt;Keeping your app performant &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/indexeddb-best-practices/#keeping-your-app-performant&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the key features of IndexedDB is its asynchronous API, but don&#39;t let that fool you into
thinking you don&#39;t need to worry about performance when using it. There are a number of cases where
improper usage can still block the main thread, which can lead to jank and unresponsiveness.&lt;/p&gt;
&lt;p&gt;As a general rule, reads and writes to IndexedDB should not be larger than required for the data
being accessed.&lt;/p&gt;
&lt;p&gt;While IndexedDB makes is possible to store large, nested objects as a single record (and doing so is
admittedly quite convenient from a developer perspective), this practice should be avoided. The
reason is because when IndexedDB stores an object, it must first create a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Web_Workers_API/Structured_clone_algorithm&quot; rel=&quot;noopener&quot;&gt;structured clone&lt;/a&gt;
of that object, and the structured cloning process happens on the main thread. The larger the
object, the longer the blocking time will be.&lt;/p&gt;
&lt;p&gt;This presents some challenges when planning how to persist application state to IndexedDB, as most
of the popular state management libraries (like &lt;a href=&quot;http://redux.js.org/&quot; rel=&quot;noopener&quot;&gt;Redux&lt;/a&gt;) work by managing your
entire state tree as a single JavaScript object.&lt;/p&gt;
&lt;p&gt;While managing state in this way has many benefits (e.g. it makes your code easy to reason about and
debug), and while simply storing the entire state tree as a single record in IndexedDB may be
tempting and convenient, doing this after every change (even if throttled/debounced) will result in
unnecessary blocking of the main thread, it will increase the likelihood of write errors, and in
some cases it will even cause the browser tab to crash or become unresponsive.&lt;/p&gt;
&lt;p&gt;Instead of storing the entire state tree in a single record, you should break it up into individual
records and only update the records that actually change.&lt;/p&gt;
&lt;p&gt;The same is true if you store large items like images, music, or video in IndexedDB. Store each item
with its own key rather than inside a larger object, so that you can retrieve the structured data
without paying the cost of also retrieving the binary file.&lt;/p&gt;
&lt;p&gt;As with most best practices, this is not an all-or-nothing rule. In cases where it&#39;s not feasible to
break up a state object and just write the minimal change-set, breaking up the data into sub-trees
and only writing those is still preferable to always writing the entire state tree. Little
improvements are better than no improvements at all.&lt;/p&gt;
&lt;p&gt;Lastly, you should always be &lt;a href=&quot;https://web.dev/user-centric-performance-metrics/&quot;&gt;measuring the performance impact&lt;/a&gt;
of the code you write. While it&#39;s true that small writes to IndexedDB will perform better than large
writes, this only matters if the writes to IndexedDB that your application is doing actually lead to
&lt;a href=&quot;https://web.dev/user-centric-performance-metrics/#custom-metrics&quot;&gt;long tasks&lt;/a&gt;
that block the main thread and degrade the user experience. It&#39;s important to measure so you
understand what you&#39;re optimizing for.&lt;/p&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/indexeddb-best-practices/#conclusions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Developers can leverage client storage mechanisms like IndexedDB to improve the user experience of
their application by not only persisting state across sessions but also by decreasing the time it
takes to load the initial state on repeat visits.&lt;/p&gt;
&lt;p&gt;While using IndexedDB properly can dramatically improve user experience, using it incorrectly or
failing to handle error cases can lead to broken apps and unhappy users.&lt;/p&gt;
&lt;p&gt;Since client storage involves many factors outside of your control, it&#39;s critical your code is well
tested and properly handles errors, even those that may initially seem unlikely to occur.&lt;/p&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
  
  <entry>
    <title>Measuring the Real-world Performance Impact of Service Workers</title>
    <link href="https://web.dev/service-worker-perf/"/>
    <updated>2016-07-22T00:00:00Z</updated>
    <id>https://web.dev/service-worker-perf/</id>
    <content type="html" mode="escaped">&lt;p&gt;One of the most significant benefits of &lt;a href=&quot;https://developers.google.com/web/fundamentals/primers/service-worker/&quot; rel=&quot;noopener&quot;&gt;service workers&lt;/a&gt; (from a performance perspective, at least) is their ability to proactively control the caching of assets. A web application that can cache all of its necessary resources should load substantially faster for returning visitors. But what do these gains actually look like to real users? And how do you even measure this?&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://events.google.com/io2016/&quot; rel=&quot;noopener&quot;&gt;Google I/O web app&lt;/a&gt; (IOWA for short) is a &lt;a href=&quot;https://developers.google.com/web/progressive-web-apps/&quot; rel=&quot;noopener&quot;&gt;progressive web app&lt;/a&gt; that leveraged most of the new capabilities offered by service workers to deliver a rich, app-like experience to its users. It also used Google Analytics to capture key performance data and usage patterns from its large and diverse user audience.&lt;/p&gt;
&lt;p&gt;This case study explores how IOWA used Google Analytics to answer key performance questions and report on the real-world impact of service workers.&lt;/p&gt;
&lt;h2 id=&quot;starting-with-the-questions&quot;&gt;Starting with the questions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#starting-with-the-questions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Any time you implement analytics in a website or application, it&#39;s important to start by identifying the questions you&#39;re trying to answer from the data you&#39;ll be collecting.&lt;/p&gt;
&lt;p&gt;While we had several questions we wanted to answer, for the purposes of this case study, let&#39;s focus on two of the more interesting ones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Is service worker caching more performant than the existing HTTP caching mechanisms available in all browsers?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We already expect pages to load faster for returning visitors than for new visitors since browsers can cache requests and serve them instantly on repeat visits.&lt;/p&gt;
&lt;p&gt;Service workers offers alternative caching capabilities that give developers fine-grained control over exactly what and how caching is done. In IOWA, we optimized our service worker implementation so that every asset would be cached, so returning visitors could use the app completely offline.&lt;/p&gt;
&lt;p&gt;But would this effort be any better than what the browser already does by default? And if so, how much better? &lt;a href=&quot;https://web.dev/service-worker-perf/#footnotes&quot;&gt;1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. How does service worker impact the experience of the site loading?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In other words, how fast does it &lt;em&gt;feel&lt;/em&gt; like the site is loading, regardless of the actual load times as measured by traditional page load metrics?&lt;/p&gt;
&lt;p&gt;Answering questions about how an experience feels is obviously not an easy task, and no metric is going to perfectly represent such a subjective sentiment. That being said, there are definitely some metrics that are better than others, so choosing the right ones is important.&lt;/p&gt;
&lt;h2 id=&quot;choosing-the-right-metric&quot;&gt;Choosing the right metric &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#choosing-the-right-metric&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Google Analytics, by default, tracks &lt;a href=&quot;https://support.google.com/analytics/answer/1205784&quot; rel=&quot;noopener&quot;&gt;page load times&lt;/a&gt; (via the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Navigation_timing_API&quot; rel=&quot;noopener&quot;&gt;Navigation Timing API&lt;/a&gt;) for 1% of a site&#39;s visitors, and it makes that data available via metrics like &lt;a href=&quot;https://web.dev/analytics/devguides/reporting/core/dimsmets#view=detail&amp;amp;group=site_speed&amp;amp;jump=ga_avgpageloadtime&quot;&gt;Avg. Page Load Time&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avg. Page Load Time&lt;/em&gt; is a good metric for answering our first question, but it&#39;s not a particularly good metric for answering the second. For one thing the &lt;code&gt;load&lt;/code&gt; event doesn&#39;t necessarily correspond to the moment when the user can actually interact with the app. Moreover, two apps with the exact same load time may &lt;em&gt;feel&lt;/em&gt; like they load much differently. For example, a site with a splash screen or a loading indicator probably feels like it loads much faster than a site that just shows a blank page for several seconds.&lt;/p&gt;
&lt;p&gt;In IOWA, we showed a splash screen countdown animation that (in my opinion) very successfully served to entertain the user while the rest of the app loaded in the background. Because of this, tracking how long it takes the splash screen to appear makes a lot more sense as a way to measure perceived load performance. We chose the metric &lt;strong&gt;time to first paint&lt;/strong&gt; to get this value.&lt;/p&gt;
&lt;p&gt;Once we decided on the questions we wanted to answer and identified the metrics that would be useful in answering them, it was time to implement Google Analytics and start measuring.&lt;/p&gt;
&lt;h2 id=&quot;the-analytics-implementation&quot;&gt;The analytics implementation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#the-analytics-implementation&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;ve used Google Analytics before, then you&#39;re probably familiar with the &lt;a href=&quot;https://web.dev/analytics/devguides/collection/analyticsjs/#alternative_async_tracking_snippet&quot;&gt;recommended JavaScript tracking snippet&lt;/a&gt;. It looks like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ga&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ga&lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ga&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;q&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;ga&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;q&lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arguments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;ga&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;l&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;create&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;UA-XXXXX-Y&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;auto&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pageview&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://www.google-analytics.com/analytics.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The first line in the above code initializes a global &lt;code&gt;ga()&lt;/code&gt; function (if it doesn&#39;t already exist), and the last line asynchronously downloads the &lt;code&gt;analytics.js&lt;/code&gt; library.&lt;/p&gt;
&lt;p&gt;The middle part contains these two lines:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;create&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;UA-XXXXX-Y&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;auto&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pageview&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;These two &lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/analyticsjs/how-analyticsjs-works#the_ga_command_queue&quot; rel=&quot;noopener&quot;&gt;commands&lt;/a&gt; track what pages are visited by people going to your site, but &lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/analyticsjs/#what_data_does_the_tracking_snippet_capture&quot; rel=&quot;noopener&quot;&gt;not much more&lt;/a&gt;. If you want to track additional user interactions, you have to do so yourself.&lt;/p&gt;
&lt;p&gt;For IOWA, we wanted to track two additional things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The elapsed time between when the page first starts to load and when pixels appear on the screen.&lt;/li&gt;
&lt;li&gt;Whether or not a service worker is controlling the page. With this information we could segment our reports to compare the results with and without service worker.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;capturing-time-to-first-paint&quot;&gt;Capturing time to first paint &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#capturing-time-to-first-paint&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some browsers record the precise time at which the first pixel is painted to the screen, and they make that time available to developers. That value, compared with the &lt;code&gt;navigationStart&lt;/code&gt; value exposed via the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Navigation_timing_API&quot; rel=&quot;noopener&quot;&gt;Navigation Timing API&lt;/a&gt; gives us a very accurate accounting of how much time has passed between when the user initially requested the page and when they first saw something.&lt;/p&gt;
&lt;p&gt;As I already mentioned, time to first paint is an important metric to measure because it&#39;s the first point at which a user experiences the load speed of your site. It&#39;s the first impression users get, and a good first impression can positively affect the rest of the user experience.&lt;a href=&quot;https://web.dev/service-worker-perf/#footnotes&quot;&gt;2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To get the first paint value in browsers that expose it, we created the &lt;code&gt;getTimeToFirstPaintIfSupported&lt;/code&gt; utility function:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTimeToFirstPaintIfSupported&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Ignores browsers that don&#39;t support the Performance Timing API.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;performance &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timing&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; navTiming &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timing&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; navStart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; navTiming&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;navigationStart&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fpTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// If chrome, get first paint time from `chrome.loadTimes`.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chrome &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loadTimes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      fpTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;loadTimes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firstPaintTime &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// If IE/Edge, use the prefixed `msFirstPaint` property.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// See http://msdn.microsoft.com/ff974719&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navTiming&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;msFirstPaint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      fpTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; navTiming&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;msFirstPaint&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fpTime &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; navStart&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fpTime &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; navStart&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Important:&lt;/strong&gt; the &lt;code&gt;firstpaint&lt;/code&gt; values referenced in the above function are part of vendor-specific APIs. They are non-standard and subject to change (or removal) at any time. We chose to implement them in IOWA knowing the site would have a limited lifespan. For most sites, it&#39;s best to use other &lt;a href=&quot;https://speedcurve.com/blog/user-timing-and-custom-metrics/&quot;&gt;user timing techniques&lt;/a&gt; until a &lt;code&gt;firstpaint&lt;/code&gt; (or a &lt;code&gt;firstpaint&lt;/code&gt; alternative) is standardized. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;With this, we could now write another function that &lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/analyticsjs/sending-hits&quot; rel=&quot;noopener&quot;&gt;sends&lt;/a&gt; a &lt;a href=&quot;https://support.google.com/analytics/answer/1033068#NonInteractionEvents&quot; rel=&quot;noopener&quot;&gt;non-interaction&lt;/a&gt; event with the time to first paint as its value:&lt;a href=&quot;https://web.dev/service-worker-perf/#footnotes&quot;&gt;3&lt;/a&gt;&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendTimeToFirstPaint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; timeToFirstPaint &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTimeToFirstPaintIfSupported&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeToFirstPaint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;eventCategory&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Performance&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;eventAction&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;firstpaint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Rounds to the nearest millisecond since&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// event values in Google Analytics must be integers.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;eventValue&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeToFirstPaint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Sends this as a non-interaction event,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// so it doesn&#39;t affect bounce rate.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;nonInteraction&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;After writing both of these functions, our tracking code looks like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Creates the tracker object.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;create&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;UA-XXXXX-Y&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;auto&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Sends a pageview for the initial pageload.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pageview&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Sends an event with the time to first paint data.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;sendTimeToFirstPaint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Note that depending on when the above code runs, pixels may or may not have already been painted to the screen. To ensure we always run this code after the first paint occurs, we postponed the call to &lt;code&gt;sendTimeToFirstPaint()&lt;/code&gt; until after the &lt;code&gt;load&lt;/code&gt; event. In fact, we decided to postpone sending all analytics data until after the page was loaded to ensure those requests wouldn&#39;t compete with the loading of other resources.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Creates the tracker object.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;create&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;UA-XXXXX-Y&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;auto&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Postpones sending any hits until after the page has fully loaded.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// This prevents analytics requests from delaying the loading of the page.&lt;/span&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Sends a pageview for the initial pageload.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pageview&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Sends an event with the time to first paint data.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;sendTimeToFirstPaint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The code above reports &lt;code&gt;firstpaint&lt;/code&gt; times to Google Analytics, but that&#39;s only half the story. We still needed to track the service worker status; otherwise we wouldn&#39;t be able to compare the first paint times of a service worker-controlled page and a non-controlled page.&lt;/p&gt;
&lt;h3 id=&quot;determining-service-worker-status&quot;&gt;Determining service worker status &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#determining-service-worker-status&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To determine the current status of the service worker, we created a utility function that returns  one of three values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;controlled&lt;/strong&gt;: a service worker is controlling the page. In the case of IOWA that also means all assets have been cached and the page works offline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;supported&lt;/strong&gt;: the browser supports service worker, but the service worker is not yet controlling the page. This is the expected status for first time visitors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;unsupported&lt;/strong&gt;: the user&#39;s browser does not support service worker.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getServiceWorkerStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;serviceWorker&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;controller &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;controlled&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;supported&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;unsupported&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This function got the service worker status for us; the next step was to associate this status with the data we were sending to Google Analytics.&lt;/p&gt;
&lt;h4 id=&quot;tracking-custom-data-with-custom-dimensions&quot;&gt;Tracking custom data with custom dimensions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#tracking-custom-data-with-custom-dimensions&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;By default, Google Analytics gives you lots of ways to subdivide your total traffic into groups based on attributes of the user, session, or interaction. These attributes are known as &lt;a href=&quot;https://support.google.com/analytics/answer/1033861&quot; rel=&quot;noopener&quot;&gt;dimensions&lt;/a&gt;. Common dimensions web developers care about are things like &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/dimsmets#view=detail&amp;amp;group=platform_or_device&amp;amp;jump=ga_browser&quot; rel=&quot;noopener&quot;&gt;Browser&lt;/a&gt;, &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/dimsmets#view=detail&amp;amp;group=platform_or_device&amp;amp;jump=ga_operatingsystem&quot; rel=&quot;noopener&quot;&gt;Operating System&lt;/a&gt;, or &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/dimsmets#view=detail&amp;amp;group=platform_or_device&amp;amp;jump=ga_devicecategory&quot; rel=&quot;noopener&quot;&gt;Device Category&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The service worker&#39;s status is not a dimension Google Analytics provides by default; however, Google Analytics does give you the ability to create your own &lt;a href=&quot;https://support.google.com/analytics/answer/2709828&quot; rel=&quot;noopener&quot;&gt;custom dimensions&lt;/a&gt; and define them however you want.&lt;/p&gt;
&lt;p&gt;For IOWA, we created a custom dimension called &lt;em&gt;Service Worker Status&lt;/em&gt; and set its scope to &lt;a href=&quot;https://support.google.com/analytics/answer/2709828#example-hit&quot; rel=&quot;noopener&quot;&gt;hit&lt;/a&gt; (i.e. per-interaction).&lt;a href=&quot;https://web.dev/service-worker-perf/#footer&quot;&gt;4&lt;/a&gt; Each custom dimension you create in Google Analytics is given a unique index within that property, and in your tracking code you can reference that dimension by its index. For example, if the index of the dimension we just created were 1, we could update our logic as follows to send the &lt;code&gt;firstpaint&lt;/code&gt; event to include the service worker status:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;eventCategory&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Performance&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;eventAction&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;firstpaint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Rounds to the nearest millisecond since&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// event values in Google Analytics must be integers.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;eventValue&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeToFirstPaint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Sends this as a non-interaction event,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// so it doesn&#39;t affect bounce rate.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;nonInteraction&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Sets the current service worker status as the value of&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// `dimension1` for this event.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;dimension1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getServiceWorkerStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This works, but it will only associate the service worker&#39;s status with this particular event. Since &lt;em&gt;Service Worker Status&lt;/em&gt; is something that&#39;s potentially useful to know for any interaction, it&#39;s best to include it with all data sent to Google Analytics.&lt;/p&gt;
&lt;p&gt;To include this information in all hits (e.g. all pageviews, events, etc.) we &lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/analyticsjs/accessing-trackers#updating_tracker_data&quot; rel=&quot;noopener&quot;&gt;set&lt;/a&gt; the custom dimension value on the &lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/analyticsjs/creating-trackers&quot; rel=&quot;noopener&quot;&gt;tracker&lt;/a&gt; object itself, prior to sending any data to Google Analytics.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;set&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dimension1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getServiceWorkerStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Once set, this value gets sent with all subsequent hits for the current pageload. If the user loads the page again later, a new value will likely be returned from the &lt;code&gt;getServiceWorkerStatus()&lt;/code&gt; function, and that value will be set on the tracker object.&lt;/p&gt;
&lt;p&gt;A quick note on code clarity and readability: since other people looking at this code may not know what &lt;code&gt;dimension1&lt;/code&gt; refers to, it&#39;s always best to create a variable that maps meaningful dimension names to the values analytics.js will use.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Creates a map between custom dimension names and their index.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// This is particularly useful if you define lots of custom dimensions.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; customDimensions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token constant&quot;&gt;SERVICE_WORKER_STATUS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dimension1&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Creates the tracker object.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;create&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;UA-XXXXX-Y&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;auto&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Sets the service worker status on the tracker,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// so its value is included in all future hits.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;set&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; customDimensions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SERVICE_WORKER_STATUS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getServiceWorkerStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Postpones sending any hits until after the page has fully loaded.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// This prevents analytics requests from delaying the loading of the page.&lt;/span&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Sends a pageview for the initial pageload.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;pageview&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Sends an event with the time to first paint data.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;sendTimeToFirstPaint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As I mentioned, sending the &lt;em&gt;Service Worker Status&lt;/em&gt; dimension with every hit allows us to use it when reporting on any metric.&lt;/p&gt;
&lt;p&gt;As you can see almost 85% of all pageviews for IOWA were from browsers that &lt;a href=&quot;https://jakearchibald.github.io/isserviceworkerready/&quot; rel=&quot;noopener&quot;&gt;support service worker&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-results-answering-our-questions&quot;&gt;The results: answering our questions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#the-results-answering-our-questions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once we started collecting data to answer our questions, we could report on that data to see the results. &lt;em&gt;(Note: all Google Analytics data shown here represents actual web traffic to the IOWA site from May 16-22, 2016).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The first question we had was: &lt;em&gt;Is service worker caching more performant than the existing HTTP caching mechanisms available in all browsers?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To answer that question, we created a &lt;a href=&quot;https://support.google.com/analytics/answer/1033013&quot; rel=&quot;noopener&quot;&gt;custom report&lt;/a&gt; that looked at the metric &lt;a href=&quot;https://developers.google.com//analytics/devguides/reporting/core/dimsmets#view=detail&amp;amp;group=site_speed&amp;amp;jump=ga_avgpageloadtime&quot; rel=&quot;noopener&quot;&gt;Avg. Page Load Times&lt;/a&gt; across various dimensions. This metric is well-suited to answer this question because the &lt;code&gt;load&lt;/code&gt; event fires only after all initial resources are downloaded. Thus it directly reflects the total load time for all the site&#39;s critical resources.&lt;a href=&quot;https://web.dev/service-worker-perf/#footnotes&quot;&gt;5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The dimensions we chose were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Our custom &lt;em&gt;Service Worker Status&lt;/em&gt; dimension.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/dimsmets#view=detail&amp;amp;group=user&amp;amp;jump=ga_usertype&quot; rel=&quot;noopener&quot;&gt;User Type&lt;/a&gt;, which indicates whether this is the user&#39;s first visit to the site or if they&#39;re returning. (Note: a new visitor will not have any resources cached; a returning visitor might.)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/dimsmets#view=detail&amp;amp;group=platform_or_device&amp;amp;jump=ga_devicecategory&quot; rel=&quot;noopener&quot;&gt;Device Category&lt;/a&gt;, which lets us compare the results across mobile and desktop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To control for the possibility that non-service-worker-related factors were skewing our load time results, we limited our query to only include browsers that support service worker.&lt;/p&gt;
&lt;p&gt;As you can see, visits to our app when controlled by a service worker loaded quite a bit faster than non-controlled visits, even those from returning users who likely had most of the page&#39;s resources cached. It&#39;s also interesting to notice that, on average, visitors on mobile with a service worker saw faster loads than new desktop visitors.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;…visits to our app when controlled by a service worker loaded quite a bit faster than non-controlled visits…&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can see more details in the following two tables:&lt;/p&gt;
&lt;table&gt;
  &lt;tr&gt;&lt;th colspan=&quot;4&quot;&gt;Avg. Page Load Time (Desktop)&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Service Worker Status&lt;/td&gt;
    &lt;td&gt;User Type&lt;/td&gt;
    &lt;td&gt;Avg. Page Load Time (ms)&lt;/td&gt;
    &lt;td&gt;Sample Size&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Controlled&lt;/td&gt;
    &lt;td&gt;Returning Visitor&lt;/td&gt;
    &lt;td&gt;
      2568
    &lt;/td&gt;
    &lt;td&gt;30860&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Supported&lt;/td&gt;
    &lt;td&gt;Returning Visitor
    &lt;/td&gt;
    &lt;td&gt;
      3612
    &lt;/td&gt;
    &lt;td&gt;1289&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Supported&lt;/td&gt;
    &lt;td&gt;New Visitor&lt;/td&gt;
    &lt;td&gt;
      4664
    &lt;/td&gt;
    &lt;td&gt;21991&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;table&gt;
  &lt;tr&gt;&lt;th colspan=&quot;4&quot;&gt;Avg. Page Load Time (Mobile)&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Service Worker Status&lt;/td&gt;
    &lt;td&gt;User Type&lt;/td&gt;
    &lt;td&gt;Avg. Page Load Time (ms)&lt;/td&gt;
    &lt;td&gt;Sample Size&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Controlled&lt;/td&gt;
    &lt;td&gt;Returning Visitor&lt;/td&gt;
    &lt;td&gt;
      3760
    &lt;/td&gt;
    &lt;td&gt;8162&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Supported&lt;/td&gt;
    &lt;td&gt;Returning Visitor&lt;/td&gt;
    &lt;td&gt;
      4843
    &lt;/td&gt;
    &lt;td&gt;676&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Supported&lt;/td&gt;
    &lt;td&gt;New Visitor&lt;/td&gt;
    &lt;td&gt;
      6158
    &lt;/td&gt;
    &lt;td&gt;5779&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;You may be wondering how it&#39;s possible for a returning visitor whose browser supports service worker to ever be in a non-controlled state. There are a few possible explanations for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The user left the page on the initial visit before the service worker had a chance to finish initializing.&lt;/li&gt;
&lt;li&gt;The user uninstalled the service worker via the developer tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both of these situations are relatively rare. We can see that in the data by looking at the &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/dimsmets#view=detail&amp;amp;group=site_speed&amp;amp;jump=ga_pageloadsample&quot; rel=&quot;noopener&quot;&gt;Page Load Sample&lt;/a&gt; values in the fourth column. Notice that the middle rows have a much smaller sample than the other two.&lt;/p&gt;
&lt;p&gt;Our second question was: &lt;em&gt;How does service worker impact the experience of the site loading?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To answer this question, we created another custom report for the metric &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/dimsmets#view=detail&amp;amp;group=event_tracking&amp;amp;jump=ga_avgeventvalue&quot; rel=&quot;noopener&quot;&gt;Avg. Event Value&lt;/a&gt; and filtered the results to only include our &lt;code&gt;firstpaint&lt;/code&gt; events. We used the dimensions &lt;em&gt;Device Category&lt;/em&gt; and our custom &lt;em&gt;Service Worker Status&lt;/em&gt; dimension.&lt;/p&gt;
&lt;p&gt;Contrary to what I would have expected, the service worker on mobile had much less impact on time to first paint than it did on overall page load.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;…service worker on mobile had much less impact on time to first paint than it did on overall page load.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To explore why this is the case, we have to dig deeper into the data. Averages can be good for general overviews and broad strokes, but to really get a sense of how these numbers break down across a range of users, we need to look at a distribution of &lt;code&gt;firstpaint&lt;/code&gt; times.&lt;/p&gt;
&lt;h3 id=&quot;getting-the-distribution-of-a-metric-in-google-analytics&quot;&gt;Getting the distribution of a metric in Google Analytics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#getting-the-distribution-of-a-metric-in-google-analytics&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To get the distribution of &lt;code&gt;firstpaint&lt;/code&gt; times we need access to the individual results for each event. Unfortunately, Google Analytics doesn&#39;t make this easy.&lt;/p&gt;
&lt;p&gt;Google Analytics lets us break down a report by whatever dimension we want, but it doesn&#39;t let use break down a report by metrics. That isn&#39;t to say it&#39;s impossible, it just means we had to customize our implementation a bit more to get the desired result.&lt;/p&gt;
&lt;p&gt;Since report results can only be broken down by dimensions, we had to set the metric value (in this case &lt;code&gt;firstpaint&lt;/code&gt; time) as a custom dimension on the event. To do this we created another custom dimension called &lt;em&gt;Metric Value&lt;/em&gt; and updated our &lt;code&gt;firstpaint&lt;/code&gt; tracking logic as follows:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; customDimensions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token constant&quot;&gt;SERVICE_WORKER_STATUS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dimension1&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;METRIC_VALUE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dimension2&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendTimeToFirstPaint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; timeToFirstPaint &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTimeToFirstPaintIfSupported&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeToFirstPaint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; fields &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;eventCategory&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Performance&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;eventAction&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;firstpaint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Rounds to the nearest millisecond since&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// event values in Google Analytics must be integers.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;eventValue&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeToFirstPaint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Sends this as a non-interaction event,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// so it doesn&#39;t affect bounce rate.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;nonInteraction&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;// Sets the event value as a dimension to allow for breaking down the&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// results by individual metric values at reporting time.&lt;/span&gt;&lt;br /&gt;    fields&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;customDimensions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;METRIC_VALUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fields&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventValue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;strong&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fields&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The Google Analytics web interface does not currently provide a way to visualize the distribution of arbitrary metric values, but with the help of the &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/v4/&quot; rel=&quot;noopener&quot;&gt;Google Analytics Core Reporting API&lt;/a&gt; and the &lt;a href=&quot;https://developers.google.com/chart/interactive/docs/gallery/histogram&quot; rel=&quot;noopener&quot;&gt;Google Charts library&lt;/a&gt; we could query for the raw results and then construct a histogram ourselves.&lt;/p&gt;
&lt;p&gt;For example, the following API request configuration was used to get a distribution of &lt;code&gt;firstpaint&lt;/code&gt; values on desktop with a non-controlled service worker.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  dateRanges&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;startDate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;&lt;span class=&quot;token number&quot;&gt;2016&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;-05&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;-16&lt;/span&gt;&#39;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; endDate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;&lt;span class=&quot;token number&quot;&gt;2016&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;-05&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;-22&lt;/span&gt;&#39;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  metrics&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;expression&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;ga&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;totalEvents&#39;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  dimensions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;ga&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;dimension2&#39;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  dimensionFilterClauses&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      operator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;AND&#39;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      filters&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          dimensionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;ga&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;eventAction&#39;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          operator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;EXACT&#39;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          expressions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&#39;firstpaint&#39;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          dimensionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;ga&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;dimension1&#39;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          operator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;EXACT&#39;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          expressions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&#39;supported&#39;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          dimensionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;ga&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;deviceCategory&#39;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          operator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;EXACT&#39;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          expressions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&#39;desktop&#39;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  orderBys&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      fieldName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;ga&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;dimension2&#39;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      orderType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &#39;DIMENSION_AS_INTEGER&#39;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This API request returns an array of values that look like this (Note: these are just the first five results). The results are sorted from smallest to largest, so these rows represent the fastest times.&lt;/p&gt;
&lt;table&gt;
  &lt;tr&gt;&lt;th colspan=&quot;2&quot;&gt;API Response Results (first five rows)&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;ga:dimension2&lt;/td&gt;
    &lt;td&gt;ga:totalEvents&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;4&lt;/td&gt;
    &lt;td&gt;3&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;5&lt;/td&gt;
    &lt;td&gt;2&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;6&lt;/td&gt;
    &lt;td&gt;10&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;7&lt;/td&gt;
    &lt;td&gt;8&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;8&lt;/td&gt;
    &lt;td&gt;10&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Here&#39;s what these results mean in plain English:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There were 3 events where the &lt;code&gt;firstpaint&lt;/code&gt; value was 4 ms&lt;/li&gt;
&lt;li&gt;There were 2 events where the &lt;code&gt;firstpaint&lt;/code&gt; value was 5 ms&lt;/li&gt;
&lt;li&gt;There were 10 events where the &lt;code&gt;firstpaint&lt;/code&gt; value was 6 ms&lt;/li&gt;
&lt;li&gt;There were 8 events where the &lt;code&gt;firstpaint&lt;/code&gt; value was 7 ms&lt;/li&gt;
&lt;li&gt;There were 10 events where the &lt;code&gt;firstpaint&lt;/code&gt; &lt;code&gt;value&lt;/code&gt; was 8 ms&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From these results we can extrapolate the &lt;code&gt;firstpaint&lt;/code&gt; value for every single event and create a histogram of the distribution. We did this for each of the queries we ran.&lt;/p&gt;
&lt;p&gt;Here&#39;s what the distribution looked like on desktop with a non-controlled (but supported) service worker:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Time to first paint distribution on Desktop (supported)&quot; decoding=&quot;async&quot; height=&quot;400&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Upig4DMY4d4coTSpmf9x.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The median &lt;code&gt;firstpaint&lt;/code&gt; time for the above distribution is &lt;strong&gt;912 ms&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The shape of this curve is quite typical of load time distributions. Contrast that with the histogram below which shows the distribution of first paint events for visits in which a service worker was controlling the page.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Time to first paint distribution on Desktop (controlled)&quot; decoding=&quot;async&quot; height=&quot;400&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/c9xl78q8bfDchwW0yS3Y.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Notice that when a service worker was controlling the page, many visitors experienced a near-immediate first paint, with a median of &lt;strong&gt;583 ms&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;…when a service worker was controlling the page, many visitors experienced a near-immediate first paint…&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To get a better sense of how these two distributions compare to each other, the next chart shows a merged view of the two. The histogram showing non-controlled service worker visits is overlaid on top of the histogram showing controlled visits, and both of those are overlaid on top of a histogram showing both combined.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Time to first paint distribution on Desktop&quot; decoding=&quot;async&quot; height=&quot;400&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/XO5mhd3VRT79MRHu2E9M.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;One thing I found interesting about these results was that the distribution with a controlled service worker still had a bell-shaped curve after the initial spike. I was expecting a large initial spike and then a gradual trail off, I wasn&#39;t expecting a second peak in the curve.&lt;/p&gt;
&lt;p&gt;When I looked into what might be causing this, I learned that even though a service worker can be controlling a page, its thread can be inactive. The browser does this to &lt;a href=&quot;https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#motivations&quot; rel=&quot;noopener&quot;&gt;save resources&lt;/a&gt; - obviously you don&#39;t need every service worker for every site you&#39;ve ever visited to be active and ready at a moment&#39;s notice. This explains the tail of the distribution. For some users, there was a delay while the service worker thread started up.&lt;/p&gt;
&lt;p&gt;As you can see from the distribution though, even with this initial delay, browsers with service worker delivered content faster than browsers going through the network.&lt;/p&gt;
&lt;p&gt;Here&#39;s how things looked on mobile:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Time to first paint distribution on Mobile&quot; decoding=&quot;async&quot; height=&quot;417&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/B2SBEmyFxyxbTmHkOLNE.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;While we still had a sizeable increase in near-immediate first paint times, the tail was quite a bit larger and longer. This is likely because, on mobile, starting an idle service worker thread takes longer than it does on desktop. It also explains why the difference between average &lt;code&gt;firstpaint&lt;/code&gt; time wasn&#39;t as big as I was expecting (discussed above).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;…on mobile, starting an idle service worker thread takes longer than it does on desktop.&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here is the breakdown from these variations of median first paint times on mobile and desktop grouped by service worker status:&lt;/p&gt;
&lt;table&gt;
  &lt;tr&gt;&lt;th colspan=&quot;3&quot;&gt;Median Time to First Paint (ms)&lt;/th&gt;&lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Service Worker Status&lt;/td&gt;
    &lt;td&gt;Desktop&lt;/td&gt;
    &lt;td&gt;Mobile&lt;/td&gt;
  &lt;/tr&gt;&lt;tr&gt;
  &lt;/tr&gt;&lt;tr&gt;
    &lt;td&gt;Controlled&lt;/td&gt;
    &lt;td&gt;583&lt;/td&gt;
    &lt;td&gt;1634&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;Supported (not controlled)&lt;/td&gt;
    &lt;td&gt;912&lt;/td&gt;
    &lt;td&gt;1933&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;While building these distribution visualizations took a bit more time and effort than creating a custom report in Google Analytics, they give us a far better sense of how service workers affect the performance of our site than averages alone.&lt;/p&gt;
&lt;h2 id=&quot;other-impact-from-service-workers&quot;&gt;Other impact from Service Workers &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#other-impact-from-service-workers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In addition to the performance impact, service workers also impact the user experience in several other ways that are measurable with Google Analytics.&lt;/p&gt;
&lt;h3 id=&quot;offline-access&quot;&gt;Offline access &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#offline-access&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Service workers allow users to interact with your site while offline, and while some sort of offline support is probably critical for any progressive web app, determining how critical it is in your case largely depends on how much usage is occurring offline. But how do we measure that?&lt;/p&gt;
&lt;p&gt;Sending data to Google Analytics requires an internet connection, but it doesn&#39;t require the data to be sent at the exact time the interaction took place. Google Analytics supports sending interaction data after the fact by specifying a time offset (via the &lt;a href=&quot;https://developers.google.com//analytics/devguides/collection/protocol/v1/parameters#qt&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;qt&lt;/code&gt;&lt;/a&gt; parameter).&lt;/p&gt;
&lt;p&gt;For the past two years IOWA has been using a &lt;a href=&quot;https://www.npmjs.com/package/sw-offline-google-analytics&quot; rel=&quot;noopener&quot;&gt;service worker script&lt;/a&gt; that detects failed hits to Google Analytics when the user is offline and replays them later with the &lt;code&gt;qt&lt;/code&gt; parameter.&lt;/p&gt;
&lt;p&gt;To track whether the user was online or offline, we created a custom dimension called &lt;em&gt;Online&lt;/em&gt; and set it to the value of &lt;code&gt;navigator.onLine&lt;/code&gt;, we then listened for the &lt;code&gt;online&lt;/code&gt; and &lt;code&gt;offline&lt;/code&gt; events and updated the dimension accordingly.&lt;/p&gt;
&lt;p&gt;And to get a sense for how common it was for a user to be offline while using IOWA, we created a &lt;a href=&quot;https://support.google.com/analytics/answer/3123951&quot; rel=&quot;noopener&quot;&gt;segment&lt;/a&gt; that targeted users with at least one offline interaction. Turns out, that was almost 5% of users.&lt;/p&gt;
&lt;h3 id=&quot;push-notifications&quot;&gt;Push notifications &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#push-notifications&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Service workers allow users to opt-in to receiving push notifications. In IOWA, users were notified when a session in their schedule was about to start.&lt;/p&gt;
&lt;p&gt;As with any form of notifications, it&#39;s important to find the balance between providing value to the user and annoying them. To better understand which is happening, it&#39;s important to track whether users are opting-in to receive these notifications, if they&#39;re engaging with them when they arrive, and if any users who previously opted-in change their preference and opt-out.&lt;/p&gt;
&lt;p&gt;In IOWA, we only sent notifications related to the user&#39;s personalized schedule, something only logged-in users could create. This limited the set of users who could receive notifications to logged-in users (tracked via a custom dimension called &lt;em&gt;Signed In&lt;/em&gt;) whose browsers supported push notifications (tracked via another custom dimension called &lt;em&gt;Notification Permission&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;The following report is based on the metric &lt;a href=&quot;https://developers.google.com/analytics/devguides/reporting/core/dimsmets#view=detail&amp;amp;group=user&amp;amp;jump=ga_users&quot; rel=&quot;noopener&quot;&gt;Users&lt;/a&gt; and our Notification Permission custom dimension, segmented by users who signed in at some point and whose browsers support push notifications.&lt;/p&gt;
&lt;p&gt;It&#39;s great to see that more than half of our signed-in users opted to receive push notifications.&lt;/p&gt;
&lt;h3 id=&quot;app-install-banners&quot;&gt;App install banners &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#app-install-banners&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If a progress web app meets the &lt;a href=&quot;https://developers.google.com/web/fundamentals/app-install-banners/&quot; rel=&quot;noopener&quot;&gt;criteria&lt;/a&gt; and is used frequently by a user, that user may be shown an app install banner, prompting them to add the app to their home screen.&lt;/p&gt;
&lt;p&gt;In IOWA, we tracked how often these prompts were shown to the user (and whether they were accepted) with the following code:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;beforeinstallprompt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Tracks that the user saw a prompt.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;eventCategory&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;installprompt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;eventAction&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;fired&#39;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userChoice&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;choiceResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Tracks the users choice.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;eventCategory&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;installprompt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// `choiceResult.outcome` will be &#39;accepted&#39; or &#39;dismissed&#39;.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;eventAction&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; choiceResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outcome&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// `choiceResult.platform` will be &#39;web&#39; or &#39;android&#39; if the prompt was&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// accepted, or &#39;&#39; if the prompt was dismissed.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;eventLabel&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; choiceResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;platform&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Of the users who saw an app install banner, about 10% chose to add it to their home screen.&lt;/p&gt;
&lt;h2 id=&quot;possible-tracking-improvements-for-next-time&quot;&gt;Possible tracking improvements (for next time) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#possible-tracking-improvements-for-next-time&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The analytics data we collected from IOWA this year was invaluable. But hindsight always brings to light holes and opportunities to improve things for the next time around. After finishing this year&#39;s analysis, here are two things I wish we&#39;d done differently that readers looking to implement a similar strategy might want to consider:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Track more events related to the load experience&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We tracked several events that correspond to a technical metric (e.g. &lt;a href=&quot;http://webcomponents.org/polyfills/html-imports/&quot; rel=&quot;noopener&quot;&gt;HTMLImportsLoaded&lt;/a&gt;, &lt;a href=&quot;https://github.com/webcomponents/webcomponentsjs#webcomponentsready&quot; rel=&quot;noopener&quot;&gt;WebComponentsReady&lt;/a&gt;, etc.), but since so much of the load was done asynchronously, the point at which these events fired didn&#39;t necessarily correspond with a particular moment in the overall load experience.&lt;/p&gt;
&lt;p&gt;The primary load-related event we didn&#39;t track (but wish we had) is the point at which the splash screen disappeared and the user could see the page content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Store the analytics client ID in IndexedDB&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By default, analytics.js stores the &lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/analyticsjs/cookies-user-id&quot; rel=&quot;noopener&quot;&gt;client ID field&lt;/a&gt; in the browser&#39;s cookies; unfortunately, service worker scripts cannot access cookies.&lt;/p&gt;
&lt;p&gt;This presented a problem for us when we tried to implement notification tracking. We wanted to send an event from the service worker (via the &lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/protocol/&quot; rel=&quot;noopener&quot;&gt;Measurement Protocol&lt;/a&gt;) each time a notification was sent to a user, and then track the re-engagement success of that notification if the user clicked on it and arrived back in the app.&lt;/p&gt;
&lt;p&gt;While we were able to track the success of notifications in general via the &lt;code&gt;utm_source&lt;/code&gt; &lt;a href=&quot;https://support.google.com/analytics/answer/1033863#parameters&quot; rel=&quot;noopener&quot;&gt;campaign parameter&lt;/a&gt;, we weren&#39;t able to tie a particular re-engagement session to a particular user.&lt;/p&gt;
&lt;p&gt;What we could have done to work around this limitation was store the client ID via IndexedDB in our tracking code, and then that value would have been accessible to the service worker script.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Let the service worker report online/offline status&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Inspecting &lt;code&gt;navigator.onLine&lt;/code&gt; will let you know if your browser is able to connect to the router or local area network, but it won&#39;t necessarily tell if you if the user has real connectivity. And since our offline analytics service worker script simply replayed failed hits (without modifying them, or marking them as failed), we were probably under-reporting our offline usage.&lt;/p&gt;
&lt;p&gt;In the future we should track both the status of &lt;code&gt;navigator.onLine&lt;/code&gt; as well as whether the hit was replayed by the service worker due to an initial network failure. This will give us a more accurate picture of the true offline usage.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#wrapping-up&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This case study has shown that using service worker did indeed improve the load performance of the Google I/O webapp across a wide range of browsers, networks, and devices. It has also shown that when you look at a distribution of the load data across a wide range of browsers, networks, and devices, you get much more insight into how this technology handles real-world scenarios, and you discover performance characteristics that you may not have expected.&lt;/p&gt;
&lt;p&gt;Here were some of the key takeaways from the IOWA study:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On average, pages loaded quite a bit faster when a service worker was controlling the page than they did without a service worker, for both new and returning visitors.&lt;/li&gt;
&lt;li&gt;Visits to pages controlled by a service worker loaded almost instantly for many users.&lt;/li&gt;
&lt;li&gt;Service workers, when inactive, took a bit of time to start up. However, an inactive service worker still performed better than no service worker.&lt;/li&gt;
&lt;li&gt;The startup time for an inactive service worker was longer on mobile than on desktop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the performance gains observed in one particular application are generally useful to report to the larger developer community, it&#39;s important to remember that these results are specific to the type of site IOWA is (an event site) and the type of audience IOWA has (mostly developers).&lt;/p&gt;
&lt;p&gt;If you&#39;re implementing service worker in your application, it&#39;s important that you implement your own measurement strategy so you can assess your own performance and prevent future regression. If you do, please share your results so everyone can benefit!&lt;/p&gt;
&lt;h2 id=&quot;footnotes&quot;&gt;Footnotes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-worker-perf/#footnotes&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;It&#39;s not entirely fair to compare the performance of our service worker cache implementation to the performance of our site with HTTP cache alone. Because we were optimizing IOWA for service worker, we didn&#39;t spend much time optimizing for HTTP cache. If we had, the results probably would have been different. To learn more about optimizing your site for HTTP cache, read &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching&quot; rel=&quot;noopener&quot;&gt;Optimizing Content Efficiently&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Depending on how your site loads its styles and content, it&#39;s possible that the browser is able to paint prior to content or styles being available. In such cases, &lt;code&gt;firstpaint&lt;/code&gt; may correspond to a blank white screen. If you use &lt;code&gt;firstpaint&lt;/code&gt;, it&#39;s important to ensure it corresponds to a meaningful point in the loading of your site&#39;s resources.&lt;/li&gt;
&lt;li&gt;Technically we could send a &lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/analyticsjs/user-timings&quot; rel=&quot;noopener&quot;&gt;timing&lt;/a&gt; hit (which are non-interaction by default) to capture this information instead of an event. In fact, timing hits were added to Google Analytics specifically to track load metrics like this; however, timing hits get &lt;a href=&quot;https://web.dev/analytics/devguides/collection/analyticsjs/user-timings#sampling_considerations&quot;&gt;heavily sampled&lt;/a&gt; at processing time, and their values cannot be used in &lt;a href=&quot;https://support.google.com/analytics/answer/3123951&quot; rel=&quot;noopener&quot;&gt;segments&lt;/a&gt;. Given these current limitations, non-interaction events remain better suited.&lt;/li&gt;
&lt;li&gt;To better understand what scope to give a custom dimension in Google Analytics refer to the &lt;a href=&quot;https://support.google.com/analytics/answer/2709828&quot; rel=&quot;noopener&quot;&gt;Custom Dimension&lt;/a&gt; section of the Analytics help center. It&#39;s also important to understand the Google Analytics data model, which consists of users, sessions, and interactions (hits). To learn more, watch the &lt;a href=&quot;https://analyticsacademy.withgoogle.com/course/2/unit/1/lesson/3&quot; rel=&quot;noopener&quot;&gt;Analytics Academy lesson on the Google Analytics Data Model&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;This does not account for resources lazily loaded after the load event.&lt;/li&gt;
&lt;/ol&gt;
</content>
    <author>
      <name>Philip Walton</name>
    </author>
  </entry>
</feed>
