<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rick Viscomi</title>
    <description>The latest articles on DEV Community by Rick Viscomi (@rick_viscomi).</description>
    <link>https://dev.to/rick_viscomi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F82031%2F10bed707-6461-456d-8f86-9a9081984900.jpg</url>
      <title>DEV Community: Rick Viscomi</title>
      <link>https://dev.to/rick_viscomi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rick_viscomi"/>
    <language>en</language>
    <item>
      <title>Querying parsed HTML in BigQuery</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Fri, 26 May 2023 16:05:19 +0000</pubDate>
      <link>https://dev.to/httparchive/querying-parsed-html-in-bigquery-4ia2</link>
      <guid>https://dev.to/httparchive/querying-parsed-html-in-bigquery-4ia2</guid>
      <description>&lt;p&gt;A longstanding problem in the &lt;a href="https://httparchive.org/"&gt;HTTP Archive&lt;/a&gt; dataset has been extracting insights from blobs of HTML in BigQuery. For example, take the source code of example.com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Example Domain&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"Content-type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"text/html; charset=utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;    
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Example Domain&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This domain is for use in illustrative examples in documents. You may use this
    domain in literature without prior coordination or asking for permission.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://www.iana.org/domains/example"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;More information...&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you wanted to extract the link text in the last paragraph, you could do something relatively straightforward like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 'More information...'&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p:last-child a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in BigQuery, we don't have the luxury of the &lt;code&gt;document&lt;/code&gt; object, &lt;code&gt;querySelector&lt;/code&gt;, or &lt;code&gt;textContent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead, we've had to resort to unwieldy regular expressions like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="s1"&gt;'More information...'&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;REGEXP_EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;p&amp;gt;&amp;lt;a[^&amp;gt;]*&amp;gt;([^&amp;lt;]*)&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;link_text&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks like it works, but it's brittle.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if there's text or whitespace between the elements?&lt;/li&gt;
&lt;li&gt;What if there are attributes on the paragraph?&lt;/li&gt;
&lt;li&gt;What if there's another p&amp;gt;a element pair earlier in the page?&lt;/li&gt;
&lt;li&gt;What if the page uses uppercase tag names?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It goes on and on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using regular expressions for parsing HTML seems like a good idea at first, but it becomes a nightmare as you need to ramp it up to increasingly unpredictable inputs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To avoid this headache in HTTP Archive analyses, we've resorted to &lt;a href="https://github.com/HTTPArchive/custom-metrics"&gt;custom metrics&lt;/a&gt;. These are executed on each page at runtime, and it's been really effective. It enables us to analyze both the fully rendered page as well as the static HTML. But one big limitation with custom metrics is that &lt;em&gt;they only work at runtime&lt;/em&gt;. So if we want to change the code or analyze an older dataset, we're out of luck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheerio
&lt;/h2&gt;

&lt;p&gt;While looking for a way to implement &lt;a href="https://github.com/rviscomi/capo.js"&gt;capo.js&lt;/a&gt; in BigQuery to understand how pages in HTTP Archive are ordered, I came across the &lt;a href="https://cheerio.js.org/"&gt;Cheerio&lt;/a&gt; library, which is a jQuery-like interface over an HTML parser.&lt;/p&gt;

&lt;p&gt;It works &lt;em&gt;beautifully&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Kk7SRScE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qhdpvytcc4jzlx3kt16d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Kk7SRScE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qhdpvytcc4jzlx3kt16d.png" alt="Screenshot of a BigQuery query and result showing example.com being analyzed with the CAPO custom function." width="800" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To be able to use Cheerio in BigQuery, I first needed to build a JavaScript binary that I could load into a UDF. The post &lt;a href="https://asyncq.com/using-npm-library-in-google-bigquery-udf"&gt;How To Use NPM Library in Google BigQuery UDF&lt;/a&gt; was a big help. I installed the Cheerio library locally and built it into a script with an exposed &lt;code&gt;cheerio&lt;/code&gt; global variable using Webpack.&lt;/p&gt;

&lt;p&gt;I uploaded the script to HTTP Archive's Google Cloud Storage bucket. Then in BigQuery, I was able to side-load the script into the UDF with &lt;a href="https://cloud.google.com/bigquery/docs/user-defined-functions#including-javascript-libraries"&gt;OPTIONS&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'gs://httparchive/lib/cheerio.js'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, the UDF was able to reference the cheerio object to parse the HTML input and generate the results. You can see it in action at &lt;a href="https://github.com/rviscomi/capo.js/tree/main/bigquery"&gt;capo.sql&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Querying HTML in BigQuery
&lt;/h3&gt;

&lt;p&gt;Here's a full demo of the example.com link text solution in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="n"&gt;example_html&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;example_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;
&amp;lt;!doctype html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Example Domain&amp;lt;/title&amp;gt;

    &amp;lt;meta charset="utf-8" /&amp;gt;
    &amp;lt;meta http-equiv="Content-type" content="text/html; charset=utf-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1" /&amp;gt;
    &amp;lt;style type="text/css"&amp;gt;...&amp;lt;/style&amp;gt;    
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
&amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;Example Domain&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;This domain is for use in illustrative examples in documents. You may use this
    domain in literature without prior coordination or asking for permission.&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;&amp;lt;a href="https://www.iana.org/domains/example"&amp;gt;More information...&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;getLinkText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;js&lt;/span&gt;
&lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'gs://httparchive/lib/cheerio.js'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;
try {
  const $ = cheerio.load(html);
  return $('&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;last&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="s1"&gt;').text();
} catch (e) {
  return null;
}
&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;getLinkText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example_html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;link_text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🔗 &lt;a href="https://console.cloud.google.com/bigquery?sq=226352634162:d0993fbf625d4fe986284e437d123c9a"&gt;Try it on BigQuery&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The results show it working as expected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MMke1p6m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bq7atbe47wui69js4km5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MMke1p6m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bq7atbe47wui69js4km5.png" alt="Query results" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JuQgfYPE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtxgedubjtupsktnaxge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JuQgfYPE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtxgedubjtupsktnaxge.png" alt="Cheerio screenshot as blazingly fast and incredibly efficient" width="658" height="776"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cheerio is marketed as fast and efficient.&lt;/p&gt;

&lt;p&gt;If you try to parse every HTML response body in HTTP Archive, the query will fail.&lt;/p&gt;

&lt;p&gt;Fully built, the library is 331 KB. And due to the need for storing the HTML in memory to parse it, it consumes a lot of memory for large blobs.&lt;/p&gt;

&lt;p&gt;To minimize the chances of OOM errors and speed up the query, one thing you can do is pare down the HTML to the area of interest using only the most basic regular expressions. Since the capo script is only concerned with the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element, I grabbed everything up to the closing &lt;code&gt;&amp;lt;/head&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;httparchive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CAPO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;REGEXP_EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;response_body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;'(?i)(.*&amp;lt;/head&amp;gt;)'&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there are no natural "breakpoints" in the document for your use case, you could also consider restricting the input to a certain character length like &lt;code&gt;WHERE LENGTH(response_body) &amp;lt; 1000&lt;/code&gt;. The query will work and it'll run more quickly, but the results will be biased towards smaller pages.&lt;/p&gt;

&lt;p&gt;Also, some documents may not be able to be parsed at all, resulting in exceptions. I added &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; blocks to the UDF to intercept any exceptions and return null instead.&lt;/p&gt;

&lt;p&gt;That also means that your query needs to be able to handle &lt;code&gt;null&lt;/code&gt; values instead. For example, to get the first &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element from the results, I needed to use &lt;a href="https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators#array_subscript_operator"&gt;&lt;code&gt;SAFE_OFFSET&lt;/code&gt;&lt;/a&gt; instead of plain old &lt;code&gt;OFFSET&lt;/code&gt; to avoid breaking the query on &lt;code&gt;null&lt;/code&gt; values: &lt;code&gt;elements[SAFE_OFFSET(0)]&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Cheerio is a really powerful new tool in the HTTP Archive toolbox. It unlocks new types of analysis that used to be prohibitively complex. In the capo.sql use case, I was able to extract insights about pages' &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; elements that would have only been possible with custom metrics on future datasets.&lt;/p&gt;

&lt;p&gt;I'm really interested to see what new insights are possible with this approach. Let me know your thoughts in the comments and how you plan to use it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webtransparency</category>
      <category>httparchive</category>
    </item>
    <item>
      <title>Simulating real users in the lab to debug CLS issues</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Fri, 16 Jul 2021 16:46:29 +0000</pubDate>
      <link>https://dev.to/rick_viscomi/simulating-real-users-in-the-lab-to-debug-cls-issues-3hnp</link>
      <guid>https://dev.to/rick_viscomi/simulating-real-users-in-the-lab-to-debug-cls-issues-3hnp</guid>
      <description>&lt;p&gt;A developer on Stack Overflow posted &lt;a href="https://stackoverflow.com/q/68403871/1022333" rel="noopener noreferrer"&gt;this question&lt;/a&gt; and it went something like this:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Search Console is telling me that one of my pages has poor Cumulative Layout Shift (CLS). When I visit PageSpeed Insights to debug the issue, the lab data from Lighthouse is reporting a perfect CLS score of 0.0. I'm frustrated. What do I do?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here was my response.&lt;/p&gt;




&lt;p&gt;CLS can be a difficult metric to debug using lab tools like Lighthouse because they only evaluate the initial viewport by default. In other words, they don't behave like a real user. When a real user lands on a page, they scroll around and click on things.&lt;/p&gt;

&lt;p&gt;So when trying to debug Core Web Vitals issues, especially for CLS, I'd recommend visiting the page yourself with a diagnostic tool like the &lt;a href="https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma?hl=en" rel="noopener noreferrer"&gt;Web Vitals extension for Chrome&lt;/a&gt; enabled.&lt;/p&gt;

&lt;p&gt;I visited the page and after just a second of using it I was able to incur a substantial layout shift (0.484) by scrolling the page down:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.stack.imgur.com/ooLWE.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.stack.imgur.com%2FooLWE.png" alt="enter image description here"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems like what's happening is that the ad below "category : humour" is injected into the page, and all of the contents below it shift down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.stack.imgur.com/xn9wU.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.stack.imgur.com%2Fxn9wU.png" alt="enter image description here"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To reproduce this, I used WebPageTest to simulate the real-user behavior of scrolling the page down.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://www.webpagetest.org/" rel="noopener noreferrer"&gt;webpagetest.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Enter your URL&lt;/li&gt;
&lt;li&gt;Go to the "Advanced" tab of the Advanced Settings section&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Under "Inject Script", enter this code to scroll the page by 500px:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scrollBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start the test&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can see the &lt;a href="https://www.webpagetest.org/video/compare.php?tests=210716_AiDcCE_b4ccb93cf09820aa422371691cd4aa3d-r%3A2-c%3A0&amp;amp;highlightCLS=1&amp;amp;thumbSize=200&amp;amp;ival=1000&amp;amp;end=doc" rel="noopener noreferrer"&gt;results here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.stack.imgur.com/rhot7.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.stack.imgur.com%2Frhot7.png" alt="enter image description here"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.stack.imgur.com/fPhkJ.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.stack.imgur.com%2FfPhkJ.png" alt="enter image description here"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.webpagetest.org/video/video.php?tests=210716_AiDcCE_b4ccb93cf09820aa422371691cd4aa3d-r:2-c:0&amp;amp;format=gif" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webpagetest.org%2Fvideo%2Fvideo.php%3Ftests%3D210716_AiDcCE_b4ccb93cf09820aa422371691cd4aa3d-r%3A2-c%3A0%26format%3Dgif" alt="GIF of the page load"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The filmstrip view above and &lt;a href="https://www.webpagetest.org/video/view.php?tests=210716_AiDcCE_b4ccb93cf09820aa422371691cd4aa3d-r:2-c:0" rel="noopener noreferrer"&gt;video&lt;/a&gt; demonstrate the problem. After the page partially loads and is scrolled down at the 8 second mark, we can see that the ad asynchronously loads and pushes the content below it down, incurring a layout shift with a score of 0.409, enough to cause the page experience to be evaluated as "poor".&lt;/p&gt;

&lt;p&gt;If most of your users behave like this by scrolling down shortly after the page loads, that could be why your 75th percentile CLS score is in the "poor" category.&lt;/p&gt;

&lt;p&gt;To fix these kinds of layout shifts caused by ads, refer to the suggestions at &lt;a href="https://web.dev/optimize-cls/#ads-embeds-and-iframes-without-dimensions:" rel="noopener noreferrer"&gt;https://web.dev/optimize-cls/#ads-embeds-and-iframes-without-dimensions:&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ads are one of the largest contributors to layout shifts on the web. Ad networks and publishers often support dynamic ad sizes. Ad sizes increase performance/revenue due to higher click rates and more ads competing in the auction. Unfortunately, this can lead to a suboptimal user experience due to ads pushing visible content you're viewing down the page.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>webperf</category>
      <category>webvitals</category>
    </item>
    <item>
      <title>Introducing the Core Web Vitals Technology Report</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Thu, 24 Jun 2021 16:02:56 +0000</pubDate>
      <link>https://dev.to/httparchive/introducing-the-core-web-vitals-technology-report-4pep</link>
      <guid>https://dev.to/httparchive/introducing-the-core-web-vitals-technology-report-4pep</guid>
      <description>&lt;p&gt;The technologies you use to build your website can have an effect on your ability to deliver good user experiences. Good UX is key to performing well with &lt;a href="https://web.dev/vitals/" rel="noopener noreferrer"&gt;Core Web Vitals&lt;/a&gt; (CWV), a topic which is probably top of mind for you, as it is for many other web developers now that these metrics play a role in &lt;a href="https://developers.google.com/search/blog/2021/04/more-details-page-experience" rel="noopener noreferrer"&gt;Google Search&lt;/a&gt; ranking. While web developers have had tools like &lt;a href="https://support.google.com/webmasters/answer/9205520?hl=en" rel="noopener noreferrer"&gt;Search Console&lt;/a&gt; and &lt;a href="https://developers.google.com/speed/pagespeed/insights/" rel="noopener noreferrer"&gt;PageSpeed Insights&lt;/a&gt; to get data on how their sites are performing, the web community has been lacking a tool that has operated at the macro level, giving us something more like &lt;em&gt;WebSpeed Insights&lt;/em&gt;. By combining the powers of real-user experiences in the &lt;a href="https://developers.google.com/web/tools/chrome-user-experience-report/" rel="noopener noreferrer"&gt;Chrome UX Report&lt;/a&gt; (CrUX) dataset with web technology detections in &lt;a href="https://httparchive.org/" rel="noopener noreferrer"&gt;HTTP Archive&lt;/a&gt;, we can get a glimpse into how architectural decisions like choices of CMS platform or JavaScript framework play a role in sites' CWV performance. The merger of these datasets is a dashboard called the &lt;strong&gt;&lt;a href="https://datastudio.google.com/reporting/55bc8fad-44c2-4280-aa0b-5f3f0cd3d2be/page/M6ZPC" rel="noopener noreferrer"&gt;Core Web Vitals Technology Report&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlyxhpwt6e9cmahcbmdw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlyxhpwt6e9cmahcbmdw.png" alt="Chart comparing three CMSs' Core Web Vitals performance over time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This dashboard was developed for the web community to have a shared source of truth for the way websites are both built and experienced. For example, the CWV Technology Report can tell you what percentage of websites built with WordPress pass the CWV assessment. While a number like this on its own is interesting, what's more useful is the ability to track this over time and compare it to other CMSs. And that's exactly what the dashboard offers; it's an interactive way to view how websites perform, broken down by nearly 2,000 technologies.&lt;/p&gt;

&lt;p&gt;This post is a show-and-tell. First I'd like to walk you through the dashboard and show you how to use it, then I'll tell you more about the data methodology behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the dashboard
&lt;/h2&gt;

&lt;p&gt;There are three pages in the dashboard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Technology drilldown&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Technology comparison&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Settings&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://datastudio.google.com/s/n22l8A5YSJQ" rel="noopener noreferrer"&gt;drilldown&lt;/a&gt; page lets you see how desktop and mobile experiences change over time for a single technology. The default metric is the percent of origins having good CWV, and it also supports individual CWV metrics (see the "Optional metrics" section below).&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://datastudio.google.com/s/pz3Fr3C-Hqw" rel="noopener noreferrer"&gt;comparison&lt;/a&gt; page lets you compare desktop OR mobile experiences for any number of technologies over time. Similar to the drilldown page, you can select overall CWV compliance or individual CWV metrics. Additionally, this page supports visualizing the number of origins per technology.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://datastudio.google.com/s/mLfzLSvQza0" rel="noopener noreferrer"&gt;settings&lt;/a&gt; page is where you can configure report-level preferences. There are currently two settings: categories and number of origins. Refer to &lt;a href="https://www.wappalyzer.com/technologies/" rel="noopener noreferrer"&gt;Wappalyzer&lt;/a&gt; for the list of possible categories. Use this setting to limit the related technologies in the dropdown list. You can also restrict the technologies to those with a minimum level of adoption, for example those used by at least 100 websites. This can be helpful to reduce noisiness.&lt;/p&gt;

&lt;p&gt;By default, the CWV Technology Report is configured to drill down into WordPress performance and compare WordPress, Wix, and Squarespace. This is to demonstrate the kinds of insights that are possible out-of-the-box without having to know how to configure the dashboard yourself. The full URL for the vanilla version of the dashboard is &lt;a href="https://datastudio.google.com/reporting/55bc8fad-44c2-4280-aa0b-5f3f0cd3d2be/page/M6ZPC" rel="noopener noreferrer"&gt;https://datastudio.google.com/reporting/55bc8fad-44c2-4280-aa0b-5f3f0cd3d2be/page/M6ZPC&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional metrics
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6zqr3wqghl5t4cfefkb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6zqr3wqghl5t4cfefkb.png" alt="Screenshot of the dashboard showing where to find the optional metrics button"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptao8ay2hs60r3f9l1j9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptao8ay2hs60r3f9l1j9.png" alt="Screenshot of the options in the optional metrics menu"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use the "optional metrics" feature of Data Studio to customize the dashboard and select specific CWV stats in the charts/tables as needed. The icon that looks like a chart with a gear icon is the button to select optional metrics. In the timeseries chart, you can toggle between the percent of origins having good CWV overall or specifically those with good LCP, FID, or CLS. On the table views, you can use this feature to add or remove columns, for example to see all CWV metrics separately or to focus on just one.&lt;/p&gt;

&lt;p&gt;Data Studio also enables you to share deep links into the dashboard for specific configurations. For example, here's a &lt;a href="https://datastudio.google.com/s/mmMyzuJS4hw" rel="noopener noreferrer"&gt;leaderboard of the top 10 most popular CMSs&lt;/a&gt; ordered by CWV performance as of May 2021:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;Origins&lt;/th&gt;
&lt;th&gt;Percent good CWV&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;May 2021&lt;/td&gt;
&lt;td&gt;1C-Bitrix&lt;/td&gt;
&lt;td&gt;35,385&lt;/td&gt;
&lt;td&gt;56.30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 2021&lt;/td&gt;
&lt;td&gt;TYPO3 CMS&lt;/td&gt;
&lt;td&gt;24,060&lt;/td&gt;
&lt;td&gt;54.39%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 2021&lt;/td&gt;
&lt;td&gt;Drupal&lt;/td&gt;
&lt;td&gt;115,280&lt;/td&gt;
&lt;td&gt;45.11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 2021&lt;/td&gt;
&lt;td&gt;Zendesk&lt;/td&gt;
&lt;td&gt;34,713&lt;/td&gt;
&lt;td&gt;43.26%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 2021&lt;/td&gt;
&lt;td&gt;Weebly&lt;/td&gt;
&lt;td&gt;15,920&lt;/td&gt;
&lt;td&gt;33.35%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 2021&lt;/td&gt;
&lt;td&gt;Squarespace&lt;/td&gt;
&lt;td&gt;60,316&lt;/td&gt;
&lt;td&gt;33.32%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 2021&lt;/td&gt;
&lt;td&gt;Joomla&lt;/td&gt;
&lt;td&gt;44,459&lt;/td&gt;
&lt;td&gt;32.19%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 2021&lt;/td&gt;
&lt;td&gt;Wix&lt;/td&gt;
&lt;td&gt;54,604&lt;/td&gt;
&lt;td&gt;31.52%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 2021&lt;/td&gt;
&lt;td&gt;Adobe Experience Manager&lt;/td&gt;
&lt;td&gt;15,276&lt;/td&gt;
&lt;td&gt;27.65%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;May 2021&lt;/td&gt;
&lt;td&gt;WordPress&lt;/td&gt;
&lt;td&gt;1,731,010&lt;/td&gt;
&lt;td&gt;24.53%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here are some other configurations to help you explore the data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://datastudio.google.com/s/rBB9VQCCZGI" rel="noopener noreferrer"&gt;Leaderboard of all 14 technologies that are in the CMS or Blogs categories having more than 10,000 origins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="[https://datastudio.google.com/s/otsNSd-4WyA](https://datastudio.google.com/s/otsNSd-4WyA)"&gt;Comparison of all JavaScript frameworks and libraries with "jQuery" in their name&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="[https://datastudio.google.com/s/r4hBpzxX1uk](https://datastudio.google.com/s/r4hBpzxX1uk)"&gt;A year-to-date drilldown into React CWV performance&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Feature roadmap
&lt;/h3&gt;

&lt;p&gt;There are two features missing from the dashboard that I would love to add in the near future: segmenting by &lt;a href="https://developers.google.com/web/updates/2021/03/crux-rank-magnitude" rel="noopener noreferrer"&gt;CrUX rank magnitude&lt;/a&gt; and comparing Lighthouse audit compliance. Origin popularity would be a really interesting way to slice the data and the rank magnitude dimension would enable us to see how technology adoption and CWV performance change at the head, torso, and tail of the web. Adding data from Lighthouse would enable us to get some clues into &lt;em&gt;why&lt;/em&gt; a particular technology may be better or worse with CWV. For example, if a group of websites tend to have poor LCP performance, it'd be interesting to see what loading performance audits they also tend to fail. Of course there are so many variables at play and we can't determine cause and effect, but these results could give us something to think about for further exploration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;p&gt;The CWV Technology Report is a combination of two data sources: CrUX and HTTP Archive. They are similar datasets in that they measure millions of websites, but they have their own strengths and weaknesses worth exploring.&lt;/p&gt;

&lt;p&gt;CrUX is a &lt;em&gt;field tool&lt;/em&gt;, meaning that it measures real-user experiences. It's also a public dataset, so you could see how users experience any one of over 8 million websites. This is really cool, to put it loosely, because we (as a community) have visibility into how the web as a whole is being experienced.&lt;/p&gt;

&lt;p&gt;The CrUX dataset is powered by Chrome users who enable &lt;a href="https://www.google.com/chrome/browser/privacy/whitepaper.html#usagestats" rel="noopener noreferrer"&gt;usage statistics reporting&lt;/a&gt;. Their experiences on publicly discoverable websites are aggregated together over 28-day windows and the results are published in queryable monthly data dumps on BigQuery and via the CrUX API, updated daily. CrUX measures users' experiences for each of the CWV metrics: &lt;a href="https://web.dev/lcp/" rel="noopener noreferrer"&gt;LCP&lt;/a&gt;, &lt;a href="https://web.dev/fid/" rel="noopener noreferrer"&gt;FID&lt;/a&gt;, and &lt;a href="https://web.dev/cls/" rel="noopener noreferrer"&gt;CLS&lt;/a&gt;. Using this data, we can evaluate whether the website passes the CWV assessment if 75 percent of experiences for each metric are at least as good as thresholds set by the Web Vitals program.&lt;/p&gt;

&lt;p&gt;HTTP Archive is a &lt;em&gt;lab tool&lt;/em&gt;, meaning that it measures how individual web pages are built. Like CrUX, it's a public dataset, and it's actually based on the same websites in the CrUX corpus, so we have perfect parity when combining the two sources together. HTTP Archive is powered by WebPageTest, which integrates with other lab tools like &lt;a href="https://developers.google.com/web/tools/lighthouse" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; and &lt;a href="https://www.wappalyzer.com/" rel="noopener noreferrer"&gt;Wappalyzer&lt;/a&gt; to extract fine-grained data about the page. Lighthouse runs audits against the page to determine how well-optimized it is, for example if it takes advantage of web performance best practices. Wappalyzer is an open-source tool that detects the use of technologies like an entire CMS, a specific JavaScript library, and even what programming languages are probably used on the backend. These detections are what we use in the CWV Technology Report to segment the real-user experience data from CrUX.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8edmxd2fjew7f69j8xlp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8edmxd2fjew7f69j8xlp.png" alt="Screenshot of various Ecommerce technologies' CWV performance"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Confession time! This isn't the first tool to look at CrUX data through the lens of how websites are built. &lt;a href="https://perf-track.web.app/" rel="noopener noreferrer"&gt;Perf Track&lt;/a&gt; is a report built by &lt;a href="https://twitter.com/hdjirdeh" rel="noopener noreferrer"&gt;Houssein Djirdeh&lt;/a&gt; that slices CrUX data by JavaScript frameworks. The annual &lt;a href="https://almanac.httparchive.org/en/2020/cms" rel="noopener noreferrer"&gt;CMS chapter&lt;/a&gt; of the Web Almanac slices CrUX data by (you guessed it) CMSs. What makes the CWV Technology Dashboard different is that it facilitates exploration of the data by making &lt;em&gt;all&lt;/em&gt; &lt;a href="https://www.wappalyzer.com/technologies/" rel="noopener noreferrer"&gt;1,950 technologies&lt;/a&gt; across 71 categories discoverable in a single, browseable UI. You can choose your own adventure by filtering technologies to a single category, like &lt;a href="https://datastudio.google.com/s/ibJhd2NEEkM" rel="noopener noreferrer"&gt;Ecommerce&lt;/a&gt;, and comparing platforms head-to-head to see which has more websites passing the CWV assessment.&lt;/p&gt;

&lt;p&gt;The CrUX dataset on BigQuery is aggregated at the origin level. An origin is a way to identify an entire website. For example, &lt;a href="https://httparchive.org" rel="noopener noreferrer"&gt;https://httparchive.org&lt;/a&gt; is the origin for the HTTP Archive website and it's different from &lt;a href="https://almanac.httparchive.org" rel="noopener noreferrer"&gt;https://almanac.httparchive.org&lt;/a&gt;, which is a separate origin for the Web Almanac website.&lt;/p&gt;

&lt;p&gt;HTTP Archive measures individual web pages, not entire websites. And due to capacity limitations, HTTP Archive is limited to testing one page per website. The most natural page to test for a given website is its home page, or the root page of the origin. For example, the home/root page of the HTTP Archive website is &lt;a href="https://httparchive.org/" rel="noopener noreferrer"&gt;https://httparchive.org/&lt;/a&gt; (note the trailing slash). This introduces an important assumption that we make in the CWV Technology Dashboard: an entire website's real-user experiences are attributed to the technologies detected only on its home page. It's entirely possible that many websites we test use different technologies on their interior pages, and some technologies may even be more or less likely to be used on home pages. These biases are worth acknowledging in the methodology for full transparency, but to be honest there's not a lot we at HTTP Archive can do to mitigate them without becoming a full-blown web crawler!&lt;/p&gt;
&lt;h3&gt;
  
  
  Core Web Vitals
&lt;/h3&gt;

&lt;p&gt;There may be different approaches to measure how well a website or group of websites performs with CWV. The approach used by this dashboard is designed to most closely match the &lt;a href="https://developers.google.com/speed/docs/insights/v5/about#categories" rel="noopener noreferrer"&gt;CWV assessment in PageSpeed Insights&lt;/a&gt;. CWV metrics and thresholds may change annually, but we'll do our best to keep the dashboard in sync with the state of the art.&lt;/p&gt;

&lt;p&gt;Each individual CWV metric has a threshold below which user experiences are considered "good". For example, LCP experiences under 2.5 seconds are good. A website must have at least 75% of its LCP experiences in the "good" category to be considered as having good LCP overall. If all of the CWV metrics are good, the website is said to pass the CWV assessment. Refer to the &lt;a href="https://web.dev/vitals/#core-web-vitals" rel="noopener noreferrer"&gt;official CWV documentation&lt;/a&gt; for the latest guidance on the set of metrics and thresholds.&lt;/p&gt;

&lt;p&gt;FID is an exception worth mentioning. Because it relies on user input to be measured, it doesn't occur on as many page loads as metrics like LCP and CLS. That makes it less likely to have sufficient data for pages that may not have many interactive UI elements or websites with low popularity. So the CWV Technology Dashboard replicates the behavior in PageSpeed Insights and assesses a website's CWV, even in the absence of FID data. In that case, if LCP and CLS are good, the website passes, otherwise it doesn't. In the rare case that a website is missing LCP or CLS data, it's not eligible to be assessed at all.&lt;/p&gt;

&lt;p&gt;When evaluating a group of origins, like those in the dashboard that all use the same technology, we quantify them in terms of the percentage of origins that pass the CWV assessment. This is not to be confused with the percentage of users or the percentage of experiences. Origins are aggregated in CrUX in a way that doesn't make it meaningful to combine their distributions together. So instead, we count origins as a unit: those that use jQuery, pass the CWV assessment, have sufficient FID data, have good LCP, etc.&lt;/p&gt;

&lt;p&gt;The CrUX dataset includes a &lt;code&gt;form_factor&lt;/code&gt; dimension representing the type of device the user was on. We segment all of the data in the dashboard by this dimension and call it the "Client", with values of either desktop or mobile.&lt;/p&gt;
&lt;h3&gt;
  
  
  Querying the raw data
&lt;/h3&gt;

&lt;p&gt;The dashboard is implemented in Data Studio with a BigQuery connector to power all of the technology and CWV insights. The underlying table on BigQuery is made publicly available at &lt;a href="[https://pantheon.corp.google.com/bigquery?p=httparchive&amp;amp;d=core_web_vitals&amp;amp;t=technologies&amp;amp;page=table](https://pantheon.corp.google.com/bigquery?p=httparchive&amp;amp;d=core_web_vitals&amp;amp;t=technologies&amp;amp;page=table)"&gt;&lt;code&gt;httparchive.core_web_vitals.technologies&lt;/code&gt;&lt;/a&gt;. Feel free to query this table directly to extract information about specific technology trends, or even to build your own custom dashboards or visualizations.&lt;/p&gt;

&lt;p&gt;For reference, this is the query that generated the &lt;code&gt;core_web_vitals.technologies&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;IS_GOOD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;good&lt;/span&gt; &lt;span class="n"&gt;FLOAT64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needs_improvement&lt;/span&gt; &lt;span class="n"&gt;FLOAT64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poor&lt;/span&gt; &lt;span class="n"&gt;FLOAT64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="nb"&gt;BOOL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;good&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;good&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;needs_improvement&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;poor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;IS_NON_ZERO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;good&lt;/span&gt; &lt;span class="n"&gt;FLOAT64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needs_improvement&lt;/span&gt; &lt;span class="n"&gt;FLOAT64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;poor&lt;/span&gt; &lt;span class="n"&gt;FLOAT64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="nb"&gt;BOOL&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;good&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;needs_improvement&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;poor&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;unique_categories&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;ARRAY_AGG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
  &lt;span class="nv"&gt;`httparchive.technologies.2021_05_01_mobile`&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ARRAY_TO_STRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARRAY_AGG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="k"&gt;IGNORE&lt;/span&gt; &lt;span class="n"&gt;NULLS&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;origins&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;good_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;origins_with_good_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;good_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;origins_with_good_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;good_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;origins_with_good_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;origins_with_any_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;origins_with_any_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;origins_with_any_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;good_cwv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;origins_with_good_cwv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any_lcp&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;any_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;origins_eligible_for_cwv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;SAFE_DIVIDE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COUNTIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;good_cwv&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;COUNTIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any_lcp&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;any_cls&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pct_eligible_origins_with_good_cwv&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'desktop'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'desktop'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mobile'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IS_NON_ZERO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fast_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slow_fid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;any_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IS_GOOD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fast_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slow_fid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;good_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IS_NON_ZERO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;small_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;medium_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;large_cls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;any_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IS_GOOD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;small_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;medium_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;large_cls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;good_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IS_NON_ZERO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fast_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slow_lcp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;any_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IS_GOOD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fast_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slow_lcp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;good_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IS_GOOD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fast_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slow_fid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;fast_fid&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
    &lt;span class="n"&gt;IS_GOOD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;small_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;medium_cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;large_cls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
    &lt;span class="n"&gt;IS_GOOD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fast_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_lcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slow_lcp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;good_cwv&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="nv"&gt;`chrome-ux-report.materialized.device_summary`&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2020-01-01'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt;
    &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REGEXP_REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_TABLE_SUFFIX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;'(&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s1"&gt;)_(&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s1"&gt;{2})_(&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s1"&gt;{2}).*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;'202&lt;/span&gt;&lt;span class="se"&gt;\1&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\2&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="se"&gt;\3&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;UNNEST&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;unique_categories&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENDS_WITH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_TABLE_SUFFIX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'desktop'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'desktop'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'mobile'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="nv"&gt;`httparchive.technologies.202*`&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The most idealistic goal for this dashboard is to empower influencers in the web community to make improvements to swaths of websites at scale. Web transparency projects like this one are meant to inform and inspire, whether that's instilling a sense of competitiveness with other related technologies to climb the leaderboard or giving them actionable data to make meaningful improvements to technologies under their control. Please leave a comment if you have any suggestions to help make the CWV Technology Report better!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>corewebvitals</category>
      <category>httparchive</category>
      <category>webtransparency</category>
    </item>
    <item>
      <title>Introducing the second annual Web Almanac!</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Tue, 22 Dec 2020 15:23:49 +0000</pubDate>
      <link>https://dev.to/httparchive/introducing-the-second-annual-web-almanac-3ilh</link>
      <guid>https://dev.to/httparchive/introducing-the-second-annual-web-almanac-3ilh</guid>
      <description>&lt;p&gt;The &lt;a href="https://almanac.httparchive.org/en/2020/"&gt;2020 Web Almanac&lt;/a&gt; is a free, open-source, community-made ebook whose mission is to annually track the state of the web. This report is for everyone who's ever wondered how big a typical web page is nowadays, or what the most common CSS breakpoints are, or the most popular CMS. Those questions and &lt;em&gt;many&lt;/em&gt; more are answered in this comprehensive, data-driven research project sourced from over 7 million websites.&lt;/p&gt;

&lt;p&gt;Experts in over 20 web disciplines researched and wrote chapters covering the components web pages are made of, how users experience them, how developers publish them, and how they're delivered. &lt;a href="https://almanac.httparchive.org/en/2020/"&gt;The 2020 report&lt;/a&gt; is out and I'm excited to share it with you all! &lt;em&gt;(Read the &lt;a href="https://dev.to/httparchive/the-web-almanac-2019-is-live-4f98"&gt;2019 announcement on dev.to&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This year &lt;strong&gt;&lt;a href="https://almanac.httparchive.org/en/2020/contributors"&gt;over 100 contributors&lt;/a&gt;&lt;/strong&gt; have collaborated on the report: analyzing data, authoring chapters, peer-reviewing, editing, translating, and maintaining the website. This project was only made possible through the hard work by all of the contributors collectively.  The result is a &lt;strong&gt;free&lt;/strong&gt; 500+ page ebook that you can read on our &lt;a href="https://almanac.httparchive.org/en/2020/table-of-contents"&gt;website&lt;/a&gt;, as a &lt;a href="https://almanac.httparchive.org/static/pdfs/web_almanac_2020_en.pdf"&gt;PDF (22 MB)&lt;/a&gt;, or on &lt;a href="https://play.google.com/store/books/details?id=wqcPEAAAQBAJ"&gt;Google Play&lt;/a&gt; or &lt;a href="https://www.google.com/books/edition/The_2020_Web_Almanac/wqcPEAAAQBAJ"&gt;Google Books&lt;/a&gt;. Translations are in progress in &lt;a href="https://github.com/HTTPArchive/almanac.httparchive.org/issues?q=is%3Aopen+is%3Aissue+label%3Atranslation"&gt;nine languages&lt;/a&gt;, and the 2019 edition is completely translated into Japanese with its own &lt;a href="https://www.google.com/books/edition/The_2019_Web_Almanac/tPACEAAAQBAJ"&gt;ebook&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This is a community effort and we're a friendly, inclusive group, so if you'd like to be a part of the 2019-2020 &lt;a href="https://github.com/HTTPArchive/almanac.httparchive.org/issues/923"&gt;translation effort&lt;/a&gt; or help write the 2021 edition, we'd love for you to join us! Please fill out this &lt;a href="https://forms.gle/VRBFegGAP7d99Bhp7"&gt;interest form&lt;/a&gt; to let us know how you'd like to contribute, and we'll reach out to you when it's time to start planning in mid-2021.&lt;/p&gt;

&lt;p&gt;Give it a read and let us know what you find most interesting or surprising! Here's what's inside:&lt;/p&gt;

&lt;h3&gt;
  
  
  Part I. Page Content
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/css"&gt;Chapter 1: CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/javascript"&gt;Chapter 2: JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/markup"&gt;Chapter 3: Markup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/fonts"&gt;Chapter 4: Fonts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/third-parties"&gt;Chapter 6: Third Parties&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part II. User Experience
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/seo"&gt;Chapter 7: SEO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/accessibility"&gt;Chapter 8: Accessibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/performance"&gt;Chapter 9: Performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/privacy"&gt;Chapter 10: Privacy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/security"&gt;Chapter 11: Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/mobile-web"&gt;Chapter 12: Mobile Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/capabilities"&gt;Chapter 13: Capabilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/pwa"&gt;Chapter 14: PWA&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part III. Content Publishing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/cms"&gt;Chapter 15: CMS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/jamstack"&gt;Chapter 17: Jamstack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part IV. Content Distribution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/page-weight"&gt;Chapter 18: Page Weight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/compression"&gt;Chapter 19: Compression&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/caching"&gt;Chapter 20: Caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/resource-hints"&gt;Chapter 21: Resource Hints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/http2"&gt;Chapter 22: HTTP/2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Appendices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/methodology"&gt;Methodology&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2020/contributors"&gt;Contributors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;(Chapters 5 and 16, Media and Ecommerce, are coming soon!)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webalmanac</category>
      <category>webdev</category>
      <category>community</category>
    </item>
    <item>
      <title>The mythical "fast" web page</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Tue, 01 Dec 2020 19:04:34 +0000</pubDate>
      <link>https://dev.to/rick_viscomi/the-mythical-fast-web-page-4322</link>
      <guid>https://dev.to/rick_viscomi/the-mythical-fast-web-page-4322</guid>
      <description>&lt;p&gt;Web performance can mean a lot of different things to a lot of different people. Fundamentally, it's a question of how &lt;em&gt;fast&lt;/em&gt; a web page is. But &lt;em&gt;fast to whom&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;When this page loaded moments ago, was it fast? If so, congratulations, you had a fast experience. So ask yourself, does that make this a &lt;em&gt;fast page&lt;/em&gt;? Not so fast! Just because &lt;em&gt;you&lt;/em&gt; had a fast experience doesn't mean everyone else does too. You might even revisit this page and have yourself a slow experience.&lt;/p&gt;

&lt;p&gt;Let's say that you and everyone else who load this page all have fast experiences. Surely that makes it a fast page, right? Most people would agree. Hypothetically, what if everyone's internet speeds get 100x slower overnight? Now, all experiences on this page are suddenly slow. Is this page, which is byte-for-byte identical as it was yesterday, still fast?&lt;/p&gt;

&lt;p&gt;Fast is a concept that exists in the minds of users as they browse the web. It's not that the page is fast—the &lt;em&gt;experience&lt;/em&gt; is fast.&lt;/p&gt;

&lt;p&gt;Ok, that's enough philosophy. Why does it matter? Because there's a difference between a page that's built for speed and a page that feels fast. A svelte page could feel slow to someone having network issues. A heavily unoptimized page could feel fast to someone on high-end hardware. The proportions of those types of users can determine how fast a page is experienced in aggregate, even more so than how well-optimized it actually is.&lt;/p&gt;

&lt;p&gt;How you approach measuring a web page's performance can tell you whether it's built for speed or whether it feels fast. We call them lab and field tools. Lab tools are the microscopes that inspect a page for all possible points of friction. Field tools are the binoculars that give you an overview of how users are experiencing the page.&lt;/p&gt;

&lt;p&gt;A lab tool like &lt;a href="https://webpagetest.org/"&gt;WebPageTest&lt;/a&gt; or &lt;a href="https://developers.google.com/web/tools/lighthouse"&gt;Lighthouse&lt;/a&gt; can tell you thousands of facts about how the page was built and how quickly the page loaded from its perspective. This makes lab tools irreplaceable for inspecting and diagnosing performance issues. You can visualize every step of the page load and drill down into what's holding it up. Lab tools can even make informed recommendations for things they think you should fix, saving you the investigative time and effort. But despite their advantages, lab tools can lead you astray in subtle ways.&lt;/p&gt;

&lt;p&gt;Similar to the problem of your fast experience not necessarily reflecting everyone else's, your lab test might not be configured like most users in two important ways: access and behavior. A lab tool &lt;em&gt;accesses&lt;/em&gt; a web page from a specific hardware and network configuration, which can greatly affect the page's loading performance. A lab tool might not &lt;em&gt;behave&lt;/em&gt; in ways that mimic real users either, for example the test might not be logged in, scroll the page after it loads, nor click on buttons.&lt;/p&gt;

&lt;p&gt;This problem is becoming more and more apparent as developers rightly focus on user-centric metrics. &lt;a href="https://web.dev/vitals/"&gt;Core Web Vitals&lt;/a&gt; represent a few distinct aspects of a good user experience: loading performance, input responsiveness, and layout stability. These are measured by Largest Contentful Paint (LCP), First Input Delay (FID), and Cumulative Layout Shift (CLS) respectively. So what could go wrong with measuring these metrics in the lab?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://web.dev/lcp/"&gt;LCP&lt;/a&gt; is the time that the biggest content loaded on screen. The times at which things load can be highly dependent on how fast the network is, so the lab configuration can produce wildly different LCP values based on its bandwidth and latency settings. Large content like images may also be cached and immediately available for some users, but lab tests tend to run with empty caches, necessitating another trip over the network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://web.dev/fid/"&gt;FID&lt;/a&gt; is the delay from first interacting with the page, like a click, to the time that the browser is ready to respond to it. The main thread could be so busy with script execution or DOM construction that the event handler has to wait its turn. The obvious limitation with testing a page in the lab is that there aren't any users to interact with it! There are diagnostic metrics for interactivity in the lab, like Total Blocking Time (TBT), but these don't actually measure the user experience. We can fake FID and simulate a user's click, but the questions of what to click and when to click it can be very subjective.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://web.dev/cls/"&gt;CLS&lt;/a&gt; is roughly the proportion of the viewport that shifted as a result of layout instability. A layout could have a moment of instability when elements are suddenly added or removed and the positions of neighboring contents shift. Because the layout shift score is a proportion of the viewport, CLS can be very different between phones and desktops. The type of device used or emulated in the lab directly affects how CLS is calculated. There's another issue having to do with user behavior: when to stop measuring. Lab tools tend to stop when the page is loaded, but real users are just getting started interacting with the page and potentially incurring many more layout shifts. Real users scroll and click and trigger new sorts of conditions that contribute to layout instability. Simulating these behaviors in the lab would be closer to reality but it has similar challenges to FID.&lt;/p&gt;

&lt;p&gt;This is why field data is the ground truth for how a page is experienced. At best we can only &lt;em&gt;simulate&lt;/em&gt; user experiences in the lab, and we'd still be hypothesizing how a user would access a page and how they'd behave once they get there.&lt;/p&gt;

&lt;p&gt;But wait! What if we calibrate our lab configurations based on real-user data from the field? This isn't a new idea; developers have been calibrating &lt;em&gt;access&lt;/em&gt; factors like geographic location, browser, and network speed based on field data &lt;a href="https://calendar.perfplanet.com/2012/using-rum-to-calibrate-your-synthetic-testing/"&gt;for years&lt;/a&gt;. But now it's more important than ever to calibrate &lt;em&gt;behavior&lt;/em&gt; as well. For example, we can use analytics to see what users tend to click on first and when they click on it.&lt;/p&gt;

&lt;p&gt;Some lab tools like WebPageTest are advanced enough to be able to &lt;a href="https://github.com/WPO-Foundation/webpagetest-docs/blob/master/user/Scripting.md#scripting"&gt;script&lt;/a&gt; that behavior into the test. But a popular tool like &lt;a href="https://developers.google.com/speed/pagespeed/insights/"&gt;PageSpeed Insights&lt;/a&gt; (PSI) has no configurability beyond plugging in the URL you want to test, so you need to take its lab results with a grain of salt. Keep in mind that performance is a distribution, and one lab test is just a single contrived data point.&lt;/p&gt;

&lt;p&gt;Fear not, even unrealistic lab tests can still be useful. One practical application of this is to test for worst case scenarios. You may not be able to say with certainty that anyone who visits your page will have a fast experience, but if you can make it seem fast under even the slowest conditions, that goes a long way. Stress testing your page's performance by using (or emulating) low-end hardware over strained network speeds is a great way to magnify the power of the microscope to bring more performance problems into focus. This is an opportunity to fix issues before users may even experience them.&lt;/p&gt;

&lt;p&gt;What if users aren't experiencing this slow performance because they're conditioned not to? An experience can be so poor that the user abandons it before it gets any worse. They may never come back to the site at all, in which case your field data has survivorship bias where only the &lt;em&gt;bearably slow&lt;/em&gt; experiences are measured. How many unbearably slow experiences &lt;em&gt;aren't&lt;/em&gt; you measuring? And you thought we were done with the philosophical questions!&lt;/p&gt;

&lt;p&gt;Let's stop here and recap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Individual experiences are just data points along a distribution. What feels fast depends on the conditions under which it was experienced. Everyone's conditions are different.&lt;/li&gt;
&lt;li&gt;Lab tests may not be configured to be representative of the most common experiences on the curve, or &lt;em&gt;any experience&lt;/em&gt; on the curve for that matter.&lt;/li&gt;
&lt;li&gt;User-centric metrics require extra care to ensure that behaviors are emulated faithfully in the lab.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a web development community, we need to change more than just our mindset about "fast" web pages. It's not enough to be aware of the pitfalls that can lead us astray: to avoid them requires a concerted effort between the makers and users of performance testing tools.&lt;/p&gt;

&lt;p&gt;Lab tools must be configurable to access and behave like real users. There is no one-size-fits-all lab configuration that represents how users experience all pages. Developers need to be active participants in the configuration process—not necessarily down to the Mbps of bandwidth, but they should make high-level decisions about what type of user they're simulating in the lab. This could be a manual guessing game, but at least developers are made more conscious of the relevance of the results.&lt;/p&gt;

&lt;p&gt;An even better solution would be to build stronger data bridges between field and lab tools, so that the lab tool itself can make informed recommendations about the most realistic user profiles to simulate.&lt;/p&gt;

&lt;p&gt;We're at an exciting inflection point in the power of developer tooling. As newer metrics focus on how pages are experienced from users' perspectives, we have an opportunity to rethink and reshape the ways our tools help us to measure and optimize them. By instrumenting lab tools with the behavioral characteristics of real users, we can unlock new opportunities to improve experiences beyond the page load. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>webperf</category>
    </item>
    <item>
      <title>Making a custom CrUX Dash shortcut in Chrome</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Tue, 22 Sep 2020 15:11:18 +0000</pubDate>
      <link>https://dev.to/rick_viscomi/making-a-custom-crux-dash-shortcut-in-chrome-3i4e</link>
      <guid>https://dev.to/rick_viscomi/making-a-custom-crux-dash-shortcut-in-chrome-3i4e</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2k1o5lfq04s1nuzxaash.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2k1o5lfq04s1nuzxaash.png" alt="Configuring the shortcut"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The CrUX Dashboard is a really easy way to see how real users experience the web, with data available for millions of websites. For developers who want to quickly jump into the CrUX Dashboard for a website, I made the &lt;a href="https://github.com/rviscomi/crux-dash-launcher" rel="noopener noreferrer"&gt;CrUX Dash Launcher&lt;/a&gt;, a tiny web app that takes an origin and redirects you to its dashboard, by taking advantage of URL parameters.&lt;/p&gt;

&lt;p&gt;Now there's an even easier way to launch into a customized CrUX Dashboard, using Chrome search shortcuts. Setting it up is almost as easy, as I've done here in under a minute:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/t88_MvwLrns"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Here's how to set it up yourself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://dev.tochrome://settings/searchEngines"&gt;chrome://settings/searchEngines&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;strong&gt;Add&lt;/strong&gt; and enter the following URL:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://datastudio.google.com/c/u/0/reporting/bbc5698d-57bb-4969-9e07-68810b9fa348/page/keDQB?params=%7B%22origin%22:%22%s%22%7D
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the search engine to &lt;code&gt;CrUX Dashboard&lt;/code&gt; and the keyword to something unique and easy to type like &lt;code&gt;cruxdash&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjcic25sz7v2pszwp8znf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjcic25sz7v2pszwp8znf.png" alt="Chrome search shortcut for CrUX Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can start typing &lt;code&gt;cruxdash&lt;/code&gt; in the URL bar, and after pressing the &lt;code&gt;&amp;lt;Space&amp;gt;&lt;/code&gt; key, it should prompt you to "Search CrUX Dashboard". Enter any website's origin (scheme, subdomain, and domain), eg &lt;code&gt;https://dev.to&lt;/code&gt;, and press enter to be taken directly to a &lt;a href="https://datastudio.google.com/u/0/reporting/bbc5698d-57bb-4969-9e07-68810b9fa348/page/keDQB?params=%7B%22origin%22:%22https:%2F%2Fdev.to%22%7D"&gt;custom CrUX Dashboard&lt;/a&gt; for that website. Voila!&lt;/p&gt;

</description>
      <category>chrome</category>
      <category>chromeuxreport</category>
    </item>
    <item>
      <title>The 2019 Web Almanac is now available as a free ebook!</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Sat, 23 May 2020 22:02:41 +0000</pubDate>
      <link>https://dev.to/httparchive/the-2019-web-almanac-is-now-available-as-a-free-ebook-1b67</link>
      <guid>https://dev.to/httparchive/the-2019-web-almanac-is-now-available-as-a-free-ebook-1b67</guid>
      <description>&lt;p&gt;Last year &lt;a href="https://almanac.httparchive.org/en/2019/contributors?teams=authors"&gt;29&lt;/a&gt; subject matter experts from the web community came together in a massive effort to document the state of the web, called the &lt;a href="https://almanac.httparchive.org"&gt;Web Almanac&lt;/a&gt;. They wrote about 20 topics in the areas of page content, user experience, content publishing, and distribution. Chapters include JavaScript, CSS, Performance, SEO, Ecommerce, CMS, CDN, HTTP/2, and many more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_hfTyjkE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rufd2e2iimo2505ltk0v.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_hfTyjkE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rufd2e2iimo2505ltk0v.jpg" alt="Screenshot of the Caching chapter in the 2019 Web Almanac ebook"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can &lt;a href="https://almanac.httparchive.org/static/pdfs/web_almanac_2019_en.pdf"&gt;download an ebook&lt;/a&gt; of the entire 2019 edition (for free). It's 421 pages and 18 MB of solid research and analysis from trusted web experts packaged up for ergonomic e-reading. We've also translated the entire contents into &lt;a href="https://almanac.httparchive.org/static/pdfs/web_almanac_2019_ja.pdf"&gt;Japanese&lt;/a&gt;. Or if you'd prefer to browse the content on the web, you can always visit &lt;a href="https://almanac.httparchive.org/en/2019/"&gt;almanac.httparchive.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The mission of the Web Almanac project is to document and raise awareness of the state of the web using the huge amounts of data from millions of websites in the &lt;a href="https://httparchive.org"&gt;HTTP Archive&lt;/a&gt;. This project is invaluable to understanding how the web is trending and the areas in which we need to be doing better. I hope you check it out!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webalmanac</category>
      <category>free</category>
    </item>
    <item>
      <title>YEStifications: Exploring how users engage with notification prompts in the Chrome UX Report</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Tue, 11 Feb 2020 23:02:58 +0000</pubDate>
      <link>https://dev.to/chromiumdev/yestifications-exploring-how-users-engage-with-notification-prompts-in-the-chrome-ux-report-4h7c</link>
      <guid>https://dev.to/chromiumdev/yestifications-exploring-how-users-engage-with-notification-prompts-in-the-chrome-ux-report-4h7c</guid>
      <description>&lt;h1&gt;
  
  
  📢 example.com wants to show notifications
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API"&gt;Notifications&lt;/a&gt; can be an effective way to keep users engaged, but many websites are unable to get users past the permission prompt. A common problem is prompting out of context, as soon as the page loads or without an explanation of how they will be used. So how do users tend to engage with these permission prompts? CrUX to the rescue.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developers.google.com/web/tools/chrome-user-experience-report/"&gt;Chrome UX Report&lt;/a&gt; (CrUX) now includes data from 30,000 websites on &lt;a href="https://developers.google.com/web/updates/2020/02/notification-permission-data-in-crux"&gt;users' engagement with notification prompts&lt;/a&gt;. The data is made available in a &lt;a href="https://console.cloud.google.com/bigquery?p=chrome-ux-report&amp;amp;d=all&amp;amp;t=202001&amp;amp;page=table"&gt;public BigQuery repository&lt;/a&gt;, which we'll explore in this post to see how users engage with prompts across the web. Let's dive in!&lt;/p&gt;

&lt;h1&gt;
  
  
  The state of notification permissions
&lt;/h1&gt;

&lt;p&gt;For starters, let's get a feel for the number of websites we're working with in this dataset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
  &lt;span class="nv"&gt;`chrome-ux-report.all.202001`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results show that there are 31,515 origins with permission data. An origin is the top-level address of a website, everything before the first "/" of the path, like &lt;code&gt;https://www.example.com&lt;/code&gt;. User experience data from pages on the same website are all rolled up at the origin level.&lt;/p&gt;

&lt;p&gt;Now let's look at the permission data for a single website, for example Slack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;`ignore`&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
  &lt;span class="nv"&gt;`chrome-ux-report.all.202001`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://app.slack.com'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Accept&lt;/th&gt;
&lt;th&gt;Dismiss&lt;/th&gt;
&lt;th&gt;Deny&lt;/th&gt;
&lt;th&gt;Ignore&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;92.10%&lt;/td&gt;
&lt;td&gt;5.67%&lt;/td&gt;
&lt;td&gt;1.96%&lt;/td&gt;
&lt;td&gt;0.28%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cR7rfPjb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8dj9pqf09gnrry6mt8ws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cR7rfPjb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8dj9pqf09gnrry6mt8ws.png" alt="Pie chart of the permission rates for app.slack.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the Slack app has a very good permission acceptance rate for their notification prompts at 92%. In other words, 92% of users who get the prompt end up granting Slack permission to show them notifications. About 6% of users close the prompt without explicitly deciding one way or the other, 2% of users explicitly disallow Slack from showing them notifications, and ~0% of users leave the prompt entirely untouched.&lt;/p&gt;

&lt;p&gt;But how representative is Slack of the experience across the web? Let's zoom out and see how all 30,000 websites stack up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;`ignore`&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
  &lt;span class="nv"&gt;`chrome-ux-report.all.202001`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;origin&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="nv"&gt;`ignore`&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;accept&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uj6cO8vV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b548fvocysh7n5ftewdo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uj6cO8vV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/b548fvocysh7n5ftewdo.png" alt="Graph of 30,000 websites' user engagement with notification permission prompts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What are we looking at here? The curved gray area on the top right of the chart shows the distribution of ignores across all websites with permission data. Next, the red area taking up the most space in the middle of the chart represents the proportion of user engagements that reject the permission prompt. The green area that curves down from the left edge of the chart represents the proportion of user engagements that accept the prompt. And the remaining yellow area between accept and deny is the dismissal rate.&lt;/p&gt;

&lt;p&gt;We can draw a couple of insights from this visualization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Most glaringly, there's a lot of red and not a lot of green in this chart. Users are highly likely to be denying notification permission prompts rather than accepting them. Slack is an extreme example of an outlier website with a high acceptance rate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There seems to be an inverse correlation between deny and ignore rates. As sites have smaller deny rates, their ignore rates increase. We could interpret the lack of prompt interaction as an implicit rejection of permission.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if Slack is such an outlier, what does a typical website look like?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`ignore`&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;`ignore`&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;`ignore`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ignore&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="nv"&gt;`chrome-ux-report.all.202001`&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Accept&lt;/th&gt;
&lt;th&gt;Dismiss&lt;/th&gt;
&lt;th&gt;Deny&lt;/th&gt;
&lt;th&gt;Ignore&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;16.71%&lt;/td&gt;
&lt;td&gt;23.84%&lt;/td&gt;
&lt;td&gt;40.74%&lt;/td&gt;
&lt;td&gt;18.71%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_0F__dZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/74on2cs7fp9ecbwve0x4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_0F__dZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/74on2cs7fp9ecbwve0x4.png" alt="Pie chart showing the average website's notification permission response rate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a more realistic view of the state of notification permissions. Keep in mind that we're averaging the UX for many websites together, even if they have drastically different numbers of users. So this is showing us that the average website has a notification prompt acceptance rate of 17%, 24% dismissal, 41% rejection, and 19% ignore.&lt;/p&gt;

&lt;p&gt;What else can we learn about user behavior from this dataset? We could also slice this average distribution by users' device form factor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`ignore`&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;`ignore`&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;form_factor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;`ignore`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deny&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ignore&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dismiss&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt;
    &lt;span class="nv"&gt;`chrome-ux-report.all.202001`&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;device&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;accept&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Device&lt;/th&gt;
&lt;th&gt;Accept&lt;/th&gt;
&lt;th&gt;Dismiss&lt;/th&gt;
&lt;th&gt;Deny&lt;/th&gt;
&lt;th&gt;Ignore&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;phone&lt;/td&gt;
&lt;td&gt;22.76%&lt;/td&gt;
&lt;td&gt;18.03%&lt;/td&gt;
&lt;td&gt;57.33%&lt;/td&gt;
&lt;td&gt;1.88%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tablet&lt;/td&gt;
&lt;td&gt;18.75%&lt;/td&gt;
&lt;td&gt;20.24%&lt;/td&gt;
&lt;td&gt;58.34%&lt;/td&gt;
&lt;td&gt;2.67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;desktop&lt;/td&gt;
&lt;td&gt;6.11%&lt;/td&gt;
&lt;td&gt;34.01%&lt;/td&gt;
&lt;td&gt;11.61%&lt;/td&gt;
&lt;td&gt;48.27%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RIV4DF6U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cwq8vv3zfxnn9ezxz4e7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RIV4DF6U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cwq8vv3zfxnn9ezxz4e7.png" alt="Bar charts of phone, tablet, and desktop average notification permission prompt rates"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok this is significant. There is a &lt;em&gt;huge&lt;/em&gt; difference in engagement between phones and desktops. Phone users are very unlikely to ignore the prompt, whereas desktop users ignore the prompt about half the time on average. The simplest explanation is screen size; it's a lot harder to ignore a prompt when it's taking up a larger portion of your screen. Interestingly, despite the 57% rejection rate, phone users are the most likely to accept permission prompts, at 23%, compared to just 6% on desktop. One explanation may be that a phone is a much more natural and convenient form factor on which to receive notifications because it goes where you go.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where we go from here
&lt;/h1&gt;

&lt;p&gt;Users have been feeling the frustration of intrusive notification prompts and developers have only anecdotally heard about and experienced this frustration. Now we have actionable data to shed light on the quality of experience across the web. And it's clear from the CrUX data that relatively few websites are effectively enrolling users in notifications.&lt;/p&gt;

&lt;p&gt;The Chrome team has announced that it is rolling out &lt;a href="https://blog.chromium.org/2020/01/introducing-quieter-permission-ui-for.html"&gt;changes to how prompts are shown to users&lt;/a&gt; to minimize frustration, saying "sites with very low acceptance rates will be automatically enrolled in quieter prompts". So it's especially important for site owners to be aware of their prompt performance.&lt;/p&gt;

&lt;p&gt;If your site requests permission to show notifications, &lt;strong&gt;add instrumentation&lt;/strong&gt; so you can monitor the response rates. The CrUX dataset is not a substitute for your own UX monitoring, but it can be helpful for filling in gaps, seeing how similar websites are performing, and tracking the trends across the web. Read up on &lt;a href="https://developers.google.com/web/fundamentals/push-notifications/permission-ux"&gt;best practices&lt;/a&gt; for requesting permission to improve your response rates and get more &lt;em&gt;yestifications&lt;/em&gt; than notifications. 🥁&lt;/p&gt;

&lt;p&gt;The queries shown in this post can be run on BigQuery. You're strongly encouraged to remix the queries to discover your own insights. Read more about &lt;a href="https://web.dev/chrome-ux-report-bigquery/"&gt;using CrUX on BigQuery&lt;/a&gt; to get started. If you have any questions or feedback about the data, the CrUX team would love to hear from you on any of these channels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://groups.google.com/a/chromium.org/forum/#!forum/chrome-ux-report"&gt;chrome-ux-report&lt;/a&gt; on Google Groups&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/ChromeUXReport"&gt;@ChromeUXReport&lt;/a&gt; on Twitter&lt;/li&gt;
&lt;li&gt;questions tagged with &lt;a href="https://stackoverflow.com/questions/tagged/chrome-ux-report"&gt;chrome-ux-report&lt;/a&gt; on StackOverflow&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/GoogleChrome/CrUX"&gt;GoogleChrome/CrUX&lt;/a&gt; on GitHub&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>ux</category>
      <category>datascience</category>
      <category>sql</category>
    </item>
    <item>
      <title>I created the Web Almanac. Ask me anything about the state of the web!</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Sun, 24 Nov 2019 02:17:24 +0000</pubDate>
      <link>https://dev.to/httparchive/i-created-the-web-almanac-ask-me-anything-about-the-state-of-the-web-3i3c</link>
      <guid>https://dev.to/httparchive/i-created-the-web-almanac-ask-me-anything-about-the-state-of-the-web-3i3c</guid>
      <description>&lt;p&gt;Hi everyone! I'm doing my first AMA to get people thinking about the state of the web.&lt;/p&gt;

&lt;p&gt;My day job for the past ~2 years has been web developer relations at Google. Prior to that I worked on web performance for ~4 years at YouTube. In my current role I'm a steward of web transparency datasets like the &lt;a href="https://httparchive.org/"&gt;HTTP Archive&lt;/a&gt; and &lt;a href="https://web.dev/chrome-ux-report/"&gt;Chrome UX Report&lt;/a&gt; projects. Web transparency is all about cultivating a public body of knowledge about how the web is built and experienced. I also host a video series called the &lt;a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcIBGvYSYO-VxOsaYQDw5rifJ"&gt;State of the Web&lt;/a&gt; where I interview members of the community about web trends and technologies. My job takes me all around the world to meet with developers at conferences, share transparency data, and hear their stories about building on the web.&lt;/p&gt;

&lt;p&gt;The big project I've been working on this year is the &lt;a href="https://almanac.httparchive.org/en/2019/"&gt;Web Almanac&lt;/a&gt;, the first annual edition of HTTP Archive's report on the state of the web, which launched at Chrome Dev Summit last week. I led the project and coordinated with 80+ community contributors to build everything from scratch (planning/writing content, researching stats, developing the website, etc). The end result is a massive resource that sheds a light on how the web is doing at the scale of millions of websites.&lt;/p&gt;

&lt;p&gt;Here are some of the interesting insights from each chapter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/javascript#open-source-libraries-and-frameworks"&gt;jQuery is found on 85% of web pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/css#popular-z-index-values"&gt;the largest known z-index is 780 digits (!important)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/markup#perspective-on-value-and-usage"&gt;there are only 11 types of HTML elements that are found on 90+% of web pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/media"&gt;the median web page is two-thirds images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/third-parties#data"&gt;94% of web pages contain at least one third party&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/fonts#where-did-you-get-those-web-fonts"&gt;Google Fonts makes up 75% of all pages' web fonts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/performance#first-contentful-paint"&gt;13% of websites deliver consistently fast performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/security#content-security-policy"&gt;Content Security Policy is used on 5% of web pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/accessibility#color-contrast"&gt;78% of mobile pages have color contrast issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/seo#content"&gt;the median desktop web page contains 346 words&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/pwa#service-workers"&gt;0.4% of pages register a service worker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/mobile-web#zooming-and-scaling"&gt;one third of mobile web pages disable zooming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/ecommerce"&gt;10% of pages use an ecommerce platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/cms#cms-adoption"&gt;40% of pages use a CMS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/compression#what-types-of-content-are-we-compressing"&gt;56% of HTML resources are uncompressed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/caching#overview-of-http-caching"&gt;72% of HTTP responses include a cache control header&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/cdn#cdn-adoption-and-usage"&gt;20% of web pages use a CDN for their HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/page-weight#page-weight"&gt;the median desktop page weighs 1,934 KB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/resource-hints#resource-hints"&gt;29% of web pages are using &lt;code&gt;dns-prefetch&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://almanac.httparchive.org/en/2019/http2#adoption-of-http2"&gt;54% of HTTP responses are served over HTTP/2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you find any of this interesting, I'd love to hear your questions about web transparency, the state of the web, the Almanac project, or anything. AMA!&lt;/p&gt;

</description>
      <category>ama</category>
      <category>discuss</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Web Almanac 2019 is live!</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Mon, 11 Nov 2019 05:41:29 +0000</pubDate>
      <link>https://dev.to/httparchive/the-web-almanac-2019-is-live-4f98</link>
      <guid>https://dev.to/httparchive/the-web-almanac-2019-is-live-4f98</guid>
      <description>&lt;p&gt;I finally get to share with everyone what I've been working on for so long! It's called the &lt;a href="https://almanac.httparchive.org/en/2019/"&gt;Web Almanac&lt;/a&gt; and it's a free, open source, community-made "state of the web" report.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the Web Almanac?
&lt;/h2&gt;

&lt;p&gt;Here is an excerpt of the &lt;a href="https://almanac.httparchive.org/en/2019/table-of-contents"&gt;foreword&lt;/a&gt; I wrote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The open web is an amazingly complex, evolving network of technologies. Entire industries and careers are built on the web and depend on its vibrant ecosystem to succeed. As critical as the web is, understanding how it's doing has been surprisingly elusive. Since 2010, the mission of the HTTP Archive project has been to track how the web is built, and it's been doing an amazing job of it. However, there has been one gap that has been especially challenging to close: bringing meaning to the data that the HTTP Archive project has been collecting and enabling the community to easily understand how the web is performing. That's where the Web Almanac comes in.&lt;/p&gt;

&lt;p&gt;The mission of the Web Almanac is to take the treasure trove of insights that would otherwise be accessible only to intrepid data miners, and package it up in a way that's easy to understand. This is made possible with the help of industry experts who can make sense of the data and tell us what it means. Each of the 20 chapters in the Web Almanac focuses on a specific aspect of the web, and each one has been authored and peer reviewed by experts in their field. The strength of the Web Almanac flows directly from the expertise of the people who write it.&lt;/p&gt;

&lt;p&gt;Many of the findings in the Web Almanac are worthy of celebration, but it's also an important reminder of the work still required to deliver high-quality user experiences. The data-driven analyses in each chapter are a form of accountability we all share for developing a better web. It's not about shaming those that are getting it wrong, but about shining a guiding light on the path of best practices so there is a clear, right way to do things. With the continued help of the web community, we hope to make this an annual tradition, so each year we can track our progress and make course corrections as needed.&lt;/p&gt;

&lt;p&gt;There is so much to learn in this report, so start exploring and share your takeaways with the community so we can collectively advance our understanding of the state of the web.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What's inside
&lt;/h2&gt;

&lt;p&gt;There are 20 chapters organized into four main parts exploring different aspects of the web.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part I. Page Content
&lt;/h3&gt;

&lt;p&gt;Chapter 1. &lt;a href="https://almanac.httparchive.org/en/2019/javascript"&gt;JavaScript&lt;/a&gt;&lt;br&gt;
Chapter 2. &lt;a href="https://almanac.httparchive.org/en/2019/css"&gt;CSS&lt;/a&gt;&lt;br&gt;
Chapter 3. &lt;a href="https://almanac.httparchive.org/en/2019/markup"&gt;Markup&lt;/a&gt;&lt;br&gt;
Chapter 4. &lt;a href="https://almanac.httparchive.org/en/2019/media"&gt;Media&lt;/a&gt;&lt;br&gt;
Chapter 5. &lt;a href="https://almanac.httparchive.org/en/2019/third-parties"&gt;Third Parties&lt;/a&gt;&lt;br&gt;
Chapter 6. &lt;a href="https://almanac.httparchive.org/en/2019/fonts"&gt;Fonts&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Part II. User Experience
&lt;/h3&gt;

&lt;p&gt;Chapter 7. &lt;a href="https://almanac.httparchive.org/en/2019/performance"&gt;Performance&lt;/a&gt;&lt;br&gt;
Chapter 8. &lt;a href="https://almanac.httparchive.org/en/2019/security"&gt;Security&lt;/a&gt;&lt;br&gt;
Chapter 9. &lt;a href="https://almanac.httparchive.org/en/2019/accessibility"&gt;Accessibility&lt;/a&gt;&lt;br&gt;
Chapter 10. &lt;a href="https://almanac.httparchive.org/en/2019/seo"&gt;SEO&lt;/a&gt;&lt;br&gt;
Chapter 11. &lt;a href="https://almanac.httparchive.org/en/2019/pwa"&gt;PWA&lt;/a&gt;&lt;br&gt;
Chapter 12. &lt;a href="https://almanac.httparchive.org/en/2019/mobile-web"&gt;Mobile Web&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Part III. Content Publishing
&lt;/h3&gt;

&lt;p&gt;Chapter 13. &lt;a href="https://almanac.httparchive.org/en/2019/ecommerce"&gt;Ecommerce&lt;/a&gt;&lt;br&gt;
Chapter 14. &lt;a href="https://almanac.httparchive.org/en/2019/cms"&gt;CMS&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Part IV. Content Distribution
&lt;/h3&gt;

&lt;p&gt;Chapter 15. &lt;a href="https://almanac.httparchive.org/en/2019/compression"&gt;Compression&lt;/a&gt;&lt;br&gt;
Chapter 16. &lt;a href="https://almanac.httparchive.org/en/2019/caching"&gt;Caching&lt;/a&gt;&lt;br&gt;
Chapter 17. &lt;a href="https://almanac.httparchive.org/en/2019/cdn"&gt;CDN&lt;/a&gt;&lt;br&gt;
Chapter 18. &lt;a href="https://almanac.httparchive.org/en/2019/page-weight"&gt;Page Weight&lt;/a&gt;&lt;br&gt;
Chapter 19. &lt;a href="https://almanac.httparchive.org/en/2019/resource-hints"&gt;Resource Hints&lt;/a&gt;&lt;br&gt;
Chapter 20. &lt;a href="https://almanac.httparchive.org/en/2019/http2"&gt;HTTP/2&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who made it
&lt;/h2&gt;

&lt;p&gt;The Web Almanac is a community effort. &lt;a href="https://almanac.httparchive.org/en/2019/contributors"&gt;85 people&lt;/a&gt; have contributed to the project to write, peer review, edit, and translate content, analyze and visualize the data, and build and design the website. This was an enormous effort that couldn't be done without everyone's help. You can check out each and every one of them on the &lt;a href="https://almanac.httparchive.org/en/2019/contributors"&gt;Contributors&lt;/a&gt; page to see where they helped.&lt;/p&gt;

&lt;p&gt;I assumed the role of ring leader to help guide everyone to today's launch :) (And I must say I think it went really well, considering the number of people to herd!)&lt;/p&gt;

&lt;h2&gt;
  
  
  How was it made
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://almanac.httparchive.org/en/2019/methodology"&gt;Methodology&lt;/a&gt; page goes into all the detail of how the process worked and where the results come from. There are so many amazing pieces of technology that went into this report, spanning data from over 5 million websites and consuming terabytes of queryable storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I don't want us to stop here. The web is constantly changing with new technologies and evolving adoption. I would love to see this renewed each year with fresh perspectives from different members of the community offering their take on the state of the web.&lt;/p&gt;

&lt;p&gt;If you're interested in joining us, please fill out &lt;a href="https://forms.gle/Qyf3q5pKgdH1cBhq5"&gt;this form&lt;/a&gt; and subscribe to our &lt;a href="https://github.com/HTTPArchive/almanac.httparchive.org"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In order to make the web a better place, we need to observe, quantify, and analyze it over time to make sure we're heading in the right direction. Join us for next year's Web Almanac 2020 edition!&lt;/p&gt;

</description>
      <category>webalmanac</category>
      <category>webdev</category>
      <category>community</category>
    </item>
    <item>
      <title>Fixing layout instability</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Wed, 18 Sep 2019 21:32:50 +0000</pubDate>
      <link>https://dev.to/chromiumdev/fixing-layout-instability-176c</link>
      <guid>https://dev.to/chromiumdev/fixing-layout-instability-176c</guid>
      <description>&lt;p&gt;In an earlier post I wrote about &lt;a href="https://dev.to/chromiumdev/measuring-cumulative-layout-shift-cls-in-webpagetest-5cle"&gt;measuring Cumulative Layout Shift&lt;/a&gt; (CLS) in WebPageTest. CLS is an aggregation of all layout shifts, so in this post I thought it'd be interesting to dive deeper and inspect each individual layout shift on a page to try to understand what could be causing the instability and actually try to fix the issue(s).&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring layout shifts
&lt;/h2&gt;

&lt;p&gt;Using the Layout Instability API, we can get a list of all layout shift events on a page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PerformanceObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hadRecentInput&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;layout-shift&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;buffered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces an array of layout shifts that are not preceded by input events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"entryType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layout-shift"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"startTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;210.78500000294298&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0001045969445437389&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hadRecentInput"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lastInputTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example there was a single very tiny shift of 0.01% at 210ms.&lt;/p&gt;

&lt;p&gt;Knowing the time and severity of the shift is useful to help narrow down what could have caused the shift. Let's turn back to &lt;a href="https://webpagetest.org" rel="noopener noreferrer"&gt;WebPageTest&lt;/a&gt; for a lab environment to do more testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring layout shifts in WebPageTest
&lt;/h2&gt;

&lt;p&gt;Similar to measuring CLS in WebPageTest, measuring individual layout shifts will require a custom metric. Fortunately, the process is easier now that Chrome 77 is stable. The Layout Instability API is enabled by default, so you should be able to execute that JS snippet on any website within Chrome 77 and get results immediately. In WebPageTest, you can use the default Chrome browser and not have to worry about command line flags or using Canary.&lt;/p&gt;

&lt;p&gt;So let's modify that script to produce a custom metric for WebPageTest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;LayoutShifts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PerformanceObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntries&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hadRecentInput&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;layout-shift&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;buffered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The promise in this script resolves to a JSON representation of the array rather than the array itself. This is because custom metrics can only produce primitive data types like strings or numbers.&lt;/p&gt;

&lt;p&gt;The website I'll use for the test is &lt;a href="https://ismyhostfastyet.com/" rel="noopener noreferrer"&gt;ismyhostfastyet.com&lt;/a&gt;, a site I built to compare real world loading performance of web hosts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identifying causes of layout instability
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="http://webpagetest.org/custom_metrics.php?test=190918_6E_ef3c166b4a34033171d47e389cf82939&amp;amp;run=5&amp;amp;cached=0" rel="noopener noreferrer"&gt;results&lt;/a&gt; we can see the LayoutShifts custom metric has this value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"entryType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layout-shift"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"startTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3087.2349999990547&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3422101449275362&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hadRecentInput"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lastInputTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To summarize, there is a single layout shift of 34.2% happening at 3087ms. To help identify the culprit, let's use WebPageTest's &lt;a href="http://webpagetest.org/video/compare.php?tests=190918_6E_ef3c166b4a34033171d47e389cf82939-r%3A5-c%3A0&amp;amp;thumbSize=200&amp;amp;ival=100&amp;amp;end=visual" rel="noopener noreferrer"&gt;filmstrip view&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Farg61baxb690dbn38pqc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Farg61baxb690dbn38pqc.png" alt="Two cells in the filmstrip, showing screenshots before and after the layout shift"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scrolling to the ~3 second mark in the filmstrip shows us exactly what the cause of the 34% layout shift is: the colorful table. The way the website is built is to asynchronously fetch a JSON file, then render it to a table. The table is initially empty, so waiting to fill it up when the results are loaded is causing the shift.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8oabdvlzekzq5srtvoa9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8oabdvlzekzq5srtvoa9.png" alt="Web font header appearing out of nowhere"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But that's not all. When the page is visually complete at ~4.3 seconds, we can see that the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; of the page "Is my host fast yet?" appears out of nowhere. This happens because the site uses a web font and hasn't taken any steps to optimize rendering. The layout doesn't actually appear to shift when this happens, but it's still a poor user experience to have to wait so long to read the title.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing layout instability
&lt;/h2&gt;

&lt;p&gt;Now that we know the asynchronously generated table is causing one third of the viewport to shift, it's time to fix it. We don't know the contents of the table until the JSON results are actually loaded, but we can still populate the table with some kind of &lt;em&gt;placeholder data&lt;/em&gt; so that the layout itself is relatively stable when the DOM is rendered.&lt;/p&gt;

&lt;p&gt;Here's the code to generate placeholder data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRandomFiller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;filler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;█&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;len&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filler&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRandomDistribution&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;avg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fast&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slow&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Temporary placeholder data.&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slow&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getRandomDistribution&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomFiller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomFiller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getRandomFiller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;slow&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;updateResultsTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sortResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The placeholder data is generated randomly before being sorted. It includes the "█" character repeated a random number of times to create visual placeholders for the text and a randomly generated distribution of the three main values. I also added some styles to desaturate all color from the table to make it clear that the data is not fully loaded yet.&lt;/p&gt;

&lt;p&gt;The appearance of the placeholders you use don't matter for layout stability as much as they are for assuring users that content &lt;em&gt;is&lt;/em&gt; coming and the page isn't broken.&lt;/p&gt;

&lt;p&gt;Here's what the placeholders look like while the JSON data is loading:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4t7x6h3ibkgpmr1pxgy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F4t7x6h3ibkgpmr1pxgy1.png" alt="The data table is rendered with placeholder data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Addressing the web font issue is much simpler. Because the site is using Google Fonts, we just need to pass in the &lt;code&gt;display=swap&lt;/code&gt; property in the CSS request. That's all. The Fonts API will add the &lt;code&gt;display: swap&lt;/code&gt; style in the font declaration, enabling the browser to render text in a fallback font immediately. Here's the corresponding markup with the fix included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.googleapis.com/css?family=Chivo:900&amp;amp;display=swap"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verifying the optimizations
&lt;/h2&gt;

&lt;p&gt;After rerunning the page through WebPageTest, we can generate a before and after &lt;a href="http://webpagetest.org/video/compare.php?tests=190918_6E_ef3c166b4a34033171d47e389cf82939%2C190918_WF_60f9c9a1c669b20039860c09ca27df7c&amp;amp;thumbSize=200&amp;amp;ival=100&amp;amp;end=visual" rel="noopener noreferrer"&gt;comparison&lt;/a&gt; to visualize the difference and measure the new degree of layout instability:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe23be8b4l6094cstdzgn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fe23be8b4l6094cstdzgn.png" alt="WebPageTest filmstrip showing both sites loading side-by-side with and without layout optimizations"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"entryType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layout-shift"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"startTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3070.9349999997357&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.000050272187989256116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hadRecentInput"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lastInputTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to the &lt;a href="http://webpagetest.org/custom_metrics.php?test=190918_WF_60f9c9a1c669b20039860c09ca27df7c&amp;amp;run=9&amp;amp;cached=0" rel="noopener noreferrer"&gt;custom metric&lt;/a&gt;, there is still a layout shift occurring at 3071ms (about the same time as before) but the severity of the shift is &lt;em&gt;much&lt;/em&gt; smaller: 0.005%. I can live with this.&lt;/p&gt;

&lt;p&gt;It's also clear from the filmstrip that the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; font is immediately falling back to a system font, enabling users to read it sooner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Complex websites will probably experience many more layout shifts than in this example, but the remediation process is still the same: add layout instability metrics to WebPageTest, cross reference the results with the visual loading filmstrip to identify the culprits, and implement a fix using placeholders to reserve the screen real estate.&lt;/p&gt;

&lt;h3&gt;
  
  
  (One more thing)
&lt;/h3&gt;

&lt;p&gt;It's nice to be able to run WebPageTest on a page before and after an optimization and see an improvement to a metric, but what really matters is that the user experience is actually getting better. Isn't that why we're trying to make the site better in the first place?&lt;/p&gt;

&lt;p&gt;So what would be great is if we start measuring the layout instability experiences of real users along with our traditional web performance metrics. This is a crucial piece of the optimization feedback loop because having data from the field tells us where the problems are and whether our fixes made a positive difference.&lt;/p&gt;

&lt;p&gt;In addition to collecting your own layout instability data, check out the &lt;a href="https://twitter.com/ChromeUXReport/status/1138555303379816448" rel="noopener noreferrer"&gt;Chrome UX Report&lt;/a&gt;, which includes Cumulative Layout Shift data from real user experiences on millions of websites. It allows you to find out how you (or your competitors) are performing, or you can use it to explore the state of layout instability across the web.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webpagetest</category>
      <category>ux</category>
    </item>
    <item>
      <title>[Video] The State of the Web: Developing with a global mindset</title>
      <dc:creator>Rick Viscomi</dc:creator>
      <pubDate>Sat, 07 Sep 2019 18:55:14 +0000</pubDate>
      <link>https://dev.to/chromiumdev/video-the-state-of-the-web-developing-with-a-global-mindset-1ajo</link>
      <guid>https://dev.to/chromiumdev/video-the-state-of-the-web-developing-with-a-global-mindset-1ajo</guid>
      <description>&lt;p&gt;To many web developers, having a global mindset just means translating the content into various languages. There are also cultural differences that may make a website difficult or even impossible to use. In this video with &lt;a href="https://twitter.com/sitnikcode"&gt;Andrey Sitnik&lt;/a&gt;, he and I talk about some of the tools and techniques for inclusivity.&lt;/p&gt;

&lt;p&gt;Some highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users' names can be &lt;em&gt;anything&lt;/em&gt; and might not fit the template of your sign up form. Rather than forcing a first and last name, allow them to enter their name into a single "Name" field. And whatever you do, don't enforce minimum character limits!&lt;/li&gt;
&lt;li&gt;The browsers you support and test with can have a big effect on who is able to use your website. There are pockets of the world where some browsers are more popular than others and the distribution of popularity is not evenly spread.&lt;/li&gt;
&lt;li&gt;Having fast and reliable access to the internet can be a luxury in some parts of the world. Even in developed countries, connectivity can be intermittent, so building offline resilience lowers the barrier to use.&lt;/li&gt;
&lt;li&gt;Expand your global mindset by busting out of your tech bubble. If you have the means, attend conferences in parts of the world where you wouldn't normally go. Something everyone can do is simply follow underrepresented people on social media to be exposed to new and different ideas. When you reshare other people's content, think twice about creating an echo chamber and instead consider amplifying underrepresented voices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What other takeaways resonated with you? &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/F2N-evGOcxc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
  </channel>
</rss>
