<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://web.dev/</id>
  <title>Kayce Basques on web.dev</title>
  <updated>2026-04-15T23:21:06Z</updated>
  <author>
    <name>Kayce Basques</name>
  </author>
  <link href="https://web.dev/authors/kaycebasques/feed.xml" rel="self"/>
  <link href="https://web.dev/"/>
  <icon>https://web-dev.imgix.net/image/admin/7GdPR4YDRHSS6llepBOd.jpg?auto=format</icon>
  <logo>https://web.dev/images/shared/rss-banner.png</logo>
  <subtitle>Technical Writer, Chrome DevTools &amp;amp; Lighthouse</subtitle>
  
  
  <entry>
    <title>Agrofy: A 70% improvement in LCP correlated to a 76% reduction in load abandonment</title>
    <link href="https://web.dev/agrofy/"/>
    <updated>2021-03-01T00:00:00Z</updated>
    <id>https://web.dev/agrofy/</id>
    <content type="html" mode="escaped">&lt;p&gt;&lt;a href=&quot;https://www.agrofy.com.ar/&quot; rel=&quot;noopener&quot;&gt;Agrofy&lt;/a&gt; is an online marketplace for Latin
America&#39;s agribusiness market. They match up buyers and sellers of farm
machines, land, equipment, and financial services. In Q3 2020 a 4-person
development team at Agrofy spent a month optimizing their website because they
hypothesized that improved performance would lead to reduced bounce rates. They
specifically focused on improving &lt;a href=&quot;https://web.dev/lcp/&quot;&gt;LCP&lt;/a&gt;, which is one of
the &lt;a href=&quot;https://web.dev/vitals/#core-web-vitals&quot;&gt;Core Web Vitals&lt;/a&gt;. These
performance optimizations led to a 70% improvement in LCP, which correlated to a
76% reduction in load abandonment (from 3.8% to 0.9%).&lt;/p&gt;
&lt;div class=&quot;stats&quot;&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;70&lt;sub&gt;%&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;Lower LCP&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;76&lt;sub&gt;%&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;Lower load abandonment&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;problem&quot;&gt;Problem &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/agrofy/#problem&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While studying their business metrics, a development team at Agrofy noticed
that their bounce rates seemed higher than industry benchmarks. Technical
debt was also increasing in the website codebase.&lt;/p&gt;
&lt;h2 id=&quot;solution&quot;&gt;Solution &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/agrofy/#solution&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Agrofy team pitched their executives and got buy-in to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Migrate from an older, deprecated framework to a newer, actively
supported one.&lt;/li&gt;
&lt;li&gt;Optimize the load performance of the new codebase.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The migration took 2 months. Aside from the 4-person development team mentioned
earlier, this migration also involved product and UX specialists and a software
architect.
The optimization project took the 4-person development team 1 month. They
focused on LCP, &lt;a href=&quot;https://web.dev/cls/&quot;&gt;CLS&lt;/a&gt; (another Core Web Vitals metric),
and &lt;a href=&quot;https://web.dev/fcp/&quot;&gt;FCP&lt;/a&gt;. Specific optimizations included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lazy loading all non-visible elements with the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Intersection_Observer_API&quot; rel=&quot;noopener&quot;&gt;Intersection Observer API&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Delivering static resources faster with a &lt;a href=&quot;https://web.dev/content-delivery-networks/&quot;&gt;content delivery
network&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/browser-level-image-lazy-loading/&quot;&gt;Lazy loading images&lt;/a&gt;
with &lt;code&gt;loading=&amp;quot;lazy&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/rendering-on-the-web/&quot;&gt;Server-side rendering&lt;/a&gt;
of
&lt;a href=&quot;https://web.dev/critical-rendering-path/&quot;&gt;critical rendering path&lt;/a&gt;
content.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/fast/#optimize-your-resource-delivery&quot;&gt;Preloading and preconnecting&lt;/a&gt;
critical resources to minimize handshake times.&lt;/li&gt;
&lt;li&gt;Using real user monitoring (RUM) tools to identify which product detail
pages were experiencing lots of layout shifts and then make adjustments to
the codebase&#39;s architecture.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check out the
&lt;a href=&quot;https://mollar-luciano.medium.com/how-agrofy-optimised-core-web-vitals-and-improved-business-metrics-2f73311bca&quot; rel=&quot;noopener&quot;&gt;Agrofy engineering blog post&lt;/a&gt;
for more technical details.&lt;/p&gt;
&lt;p&gt;After enabling the new codebase on 20% of traffic, they launched the new site to
all visitors in early September 2020.&lt;/p&gt;
&lt;h2 id=&quot;results&quot;&gt;Results &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/agrofy/#results&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The development team&#39;s optimizations led to measurable improvements in many
different metrics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LCP improved 70%.&lt;/li&gt;
&lt;li&gt;CLS improved 72%.&lt;/li&gt;
&lt;li&gt;Blocking JS requests reduced 100% and blocking CSS requests 80%.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/long-tasks-devtools/&quot;&gt;Long tasks&lt;/a&gt; reduced 72%.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/first-cpu-idle/&quot; rel=&quot;noopener&quot;&gt;First CPU Idle&lt;/a&gt; improved 25%.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Over the same time frame, real user monitoring data (also known as &lt;a href=&quot;https://web.dev/how-to-measure-speed/#lab-data-vs-field-data&quot;&gt;field
data&lt;/a&gt;) showed that
the load abandonment rate on product detail pages dropped 76%, from 3.8% to
0.9%:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;A graph showing load abandonment rate decrease of 76% on the product details page after performance optimizations.&quot; decoding=&quot;async&quot; height=&quot;461&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2lMYiXdjh5aLr4UIMVJF.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Load abandonment rate trend on product detail page.
  &lt;/figcaption&gt;
&lt;/figure&gt;
</content>
    <author>
      <name>Kayce Basques</name>
    </author>
  </entry>
  
  <entry>
    <title>Case study guidelines</title>
    <link href="https://web.dev/handbook/case-study-guidelines/"/>
    <updated>2021-01-26T00:00:00Z</updated>
    <id>https://web.dev/handbook/case-study-guidelines/</id>
    <content type="html" mode="escaped">&lt;p&gt;The goal of this guide is to help you write effective case studies.&lt;/p&gt;
&lt;h2 id=&quot;goal&quot;&gt;web.dev&#39;s goal for case studies &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#goal&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Our goal is to persuade web developers to make a change&lt;/strong&gt;. The best way to do
that is to focus on how the reader&#39;s life will get better if they make the
change.&lt;/p&gt;
&lt;p&gt;We understand that the partner&#39;s motivation is usually to increase awareness of
their company or product. We believe that it&#39;s in the partner&#39;s best interest to
not focus on themselves too much. By focusing on what&#39;s in it for web developers (the
people who are most likely to read our case studies), we maximize the odds that
people actually read and share the case study.&lt;/p&gt;
&lt;h2 id=&quot;correlation&quot;&gt;Find a compelling correlation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#correlation&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A very effective approach for case study messaging is to highlight a correlation
between the changes that the web developers made and a business metric
improvement. Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/clipchamp/&quot;&gt;Clipchamp&#39;s video editor PWA installs see a 97% monthly growth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/gravit-designer/&quot;&gt;PWA users are 2.5x more likely to purchase Gravit Designer PRO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/jdid/&quot;&gt;JD.ID improves their mobile conversion rate by 53% with caching strategies, installation, and push notifications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/rakuten-24/&quot;&gt;Rakuten 24&#39;s investment in PWA increases user retention by 450%&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/tokopedia/&quot;&gt;How focusing on web performance improved Tokopedia&#39;s click-through rate by 35%&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reason this is important is because web development teams often need to
persuade business stakeholders before they can make any significant changes
to their website or processes. Business stakeholders won&#39;t agree to these
changes unless there is evidence that the ROI is worthwhile.&lt;/p&gt;
&lt;h2 id=&quot;inverted-pyramid&quot;&gt;Organize the beginning of your content as an inverted pyramid &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#inverted-pyramid&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.nngroup.com/articles/inverted-pyramid/&quot; rel=&quot;noopener&quot;&gt;inverted pyramid&lt;/a&gt;
is a journalism best practice. Organize your content so that the most
important information is at the top, and the least important at the bottom.&lt;/p&gt;
&lt;p&gt;Think about it this way: 100K people might see a link to your case study
on social media. 50K of those people actually open the link. And only 5K
actually read through the whole thing.&lt;/p&gt;
&lt;h3 id=&quot;title&quot;&gt;Title &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#title&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The title is the single most important part of your case study because
it&#39;s what the greatest amount of people see. It should be focused
around your &lt;a href=&quot;https://web.dev/handbook/case-study-guidelines/#correlation&quot;&gt;compelling correlation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In other words, most case study titles should have these 3 parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The name of the company&lt;/li&gt;
&lt;li&gt;The web development change(s) they made&lt;/li&gt;
&lt;li&gt;The business metric improvement correlated to the change(s)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;subtitle&quot;&gt;Subtitle &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#subtitle&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The subtitle (or subhead) is the text that appears below the title. See
&lt;a href=&quot;https://web.dev/handbook/yaml-front-matter/#subhead&quot;&gt;subhead&lt;/a&gt; for an example. It also shows up
on the &lt;a href=&quot;https://web.dev/blog&quot;&gt;blog homepage&lt;/a&gt; and is the text that we suggest to search engines
to present on Search Engine Results Pages. Therefore this is the second most
important part of your case study (because it&#39;s the second-most-seen text).&lt;/p&gt;
&lt;p&gt;The subtitle is a good place to share other noteworthy correlations
(&lt;a href=&quot;https://web.dev/gravit-designer/&quot;&gt;example&lt;/a&gt;). Or, you can elaborate on the story that you
began to tell in the title (&lt;a href=&quot;https://web.dev/betty-crocker/&quot;&gt;example&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&quot;introduction&quot;&gt;Introduction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The reader, especially business executives, expect the first paragraphs of the
content to provide a summary of the most important information. A good way to
ensure that you provide all important information is to systemetically answer
the &lt;a href=&quot;https://www.workfront.com/blog/project-management-101-the-5-ws-and-1-h-that-should-be-asked-of-every-project&quot; rel=&quot;noopener&quot;&gt;Five Ws and How&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Avoid the urge to talk too much about the partner. It&#39;s OK to share some context
(1-3 sentences) so that the readers understand what the company/product does. If
it gets too long, though, it begins to sound like a sales pitch.&lt;/p&gt;
&lt;h2 id=&quot;organization&quot;&gt;General organization &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#organization&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After you have optimized your title, subtitle, and introduction, we can give you
more flexibility around how you organize the rest of your ideas. In other words,
you don&#39;t need to strictly follow an inverted pyramid approach throughout the
entire case study.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/tags/scale-on-web/&quot;&gt;Scale on web case studies&lt;/a&gt; use the following pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Highlighting the opportunity&lt;/li&gt;
&lt;li&gt;The tools they used&lt;/li&gt;
&lt;li&gt;Overall business results&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/bookmyshow/&quot;&gt;BookMyShow case study&lt;/a&gt;
demonstrates another common organization:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Challenge&lt;/li&gt;
&lt;li&gt;Solution&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;hero&quot;&gt;Hero images &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#hero&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A well-designed hero image is a good way to make your case study look
attractive and professional. In general, the web.dev team doesn&#39;t have resources
to create custom hero images for you. See &lt;a href=&quot;https://web.dev/handbook/markup-media/#hero&quot;&gt;Hero images&lt;/a&gt;
for dimension requirements.&lt;/p&gt;
&lt;h2 id=&quot;crossposting&quot;&gt;Cross-posting &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#crossposting&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If your case study has already been published on another site and you&#39;d like to mirror it on web.dev, then whether your case study gets published here becomes more complicated.&lt;/p&gt;
&lt;p&gt;In particular, requesting to cross-post articles that have already been posted on self-publishing outlets such as personal blogs are problematic, as the web.dev editorial team has a rigorous editorial process. This means that the final version of your proposed case study will likely be significantly different than what you have already published.&lt;/p&gt;
&lt;p&gt;There is also the concern of what version is canonical. While the web.dev team wants to publish compelling case studies, canonicity is important, as it can raise issues when it comes to how your article is indexed if you don&#39;t take care to mark &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Attributes/rel#canonical&quot; rel=&quot;noopener&quot;&gt;which version is canonical&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Generally speaking, you should avoid proposing case studies which have already been published elsewhere. While there may be exceptions to this rule, they are few and far between, and must be cases in which the original version has not been self-published, and is already marked as the canonical version.&lt;/p&gt;
&lt;h2 id=&quot;examples&quot;&gt;Examples &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#examples&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/tags/scale-on-web/&quot;&gt;Scale on web case studies&lt;/a&gt; case studies are the best case studies
we&#39;ve produced to date, in terms of aligning with the case study guidelines.&lt;/p&gt;
&lt;p&gt;You can check out &lt;a href=&quot;https://web.dev/tags/case-study/&quot;&gt;all of our case studies&lt;/a&gt; for more ideas,
but be aware that they may not follow the case study guidelines as
closely as the &lt;a href=&quot;https://web.dev/tags/scale-on-web/&quot;&gt;Scale on web case studies&lt;/a&gt; do.&lt;/p&gt;
&lt;h2 id=&quot;deliverables&quot;&gt;Deliverables &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/case-study-guidelines/#deliverables&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When submitting the first draft of your case study, please submit it as a Google
Doc. This helps to ensure that editorial feedback is communicated in a
consistent way in a format that is familiar for Googlers.&lt;/p&gt;
&lt;p&gt;While not &lt;em&gt;strictly&lt;/em&gt; necessary, it helps to streamline the process if source
files for figures can be provided. However, if the person responsible for
writing the case study doesn&#39;t have domain knowledge in using image editors,
the technical writing team is capable of extracting images from Google Docs if
necessary.&lt;/p&gt;
</content>
    <author>
      <name>Kayce Basques</name>
    </author>
  </entry>
  
  <entry>
    <title>Codelab: Build a push notification client</title>
    <link href="https://web.dev/push-notifications-client-codelab/"/>
    <updated>2020-11-11T00:00:00Z</updated>
    <id>https://web.dev/push-notifications-client-codelab/</id>
    <content type="html" mode="escaped">&lt;p&gt;This codelab shows you, step-by-step, how to build a push notification client.
By the end of the codelab you&#39;ll have a client that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Subscribes the user to push notifications.&lt;/li&gt;
&lt;li&gt;Receives push messages and displays them as notifications.&lt;/li&gt;
&lt;li&gt;Unsubscribes the user from push notifications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This codelab is focused on helping you learn by doing and doesn&#39;t
talk about concepts much. Check out
&lt;a href=&quot;https://web.dev/push-notifications-overview/#how&quot;&gt;How do push notifications work?&lt;/a&gt;
to learn about push notification concepts.&lt;/p&gt;
&lt;p&gt;The server code of this codelab is already complete. You&#39;ll only be
implementing the client in this codelab. To learn how to implement a
push notification server, check out &lt;a href=&quot;https://web.dev/push-notifications-server-codelab&quot;&gt;Codelab: Build a push notification
server&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://push-notifications-client-codelab-complete.glitch.me/&quot; rel=&quot;noopener&quot;&gt;push-notifications-client-codelab-complete&lt;/a&gt;
(&lt;a href=&quot;https://glitch.com/edit/#!/push-notifications-client-codelab-complete&quot; rel=&quot;noopener&quot;&gt;source&lt;/a&gt;)
to see the complete code.&lt;/p&gt;
&lt;h2 id=&quot;browser-compatibility&quot;&gt;Browser compatibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#browser-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This codelab is known to work with the following operating system and browser combinations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows: Chrome, Edge&lt;/li&gt;
&lt;li&gt;macOS: Chrome, Firefox&lt;/li&gt;
&lt;li&gt;Android: Chrome, Firefox&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This codelab is known to &lt;strong&gt;not&lt;/strong&gt; work with the following operating systems
(or operating system and browser combinations):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;macOS: Brave, Edge, Safari&lt;/li&gt;
&lt;li&gt;iOS&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;setup&quot;&gt;Setup &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#setup&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;remix&quot;&gt;Get an editable copy of the code &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#remix&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The code editor that you see to the right of these instructions will be called
the &lt;strong&gt;Glitch UI&lt;/strong&gt; throughout this codelab.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Remix to Edit&lt;/strong&gt; to make the project editable.&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; If you&#39;re in a Chrome incognito or guest window, you may have trouble completing the codelab. Consider using a signed-in profile instead. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;authentication&quot;&gt;Set up authentication &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#authentication&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before you can get push notifications working, you need to set up
your server and client with authentication keys.
See &lt;a href=&quot;https://web.dev/push-notifications-overview/#sign&quot;&gt;Sign your web push protocol requests&lt;/a&gt;
to learn why.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In the Glitch UI click &lt;strong&gt;Tools&lt;/strong&gt; and then click &lt;strong&gt;Terminal&lt;/strong&gt; to open the Glitch Terminal.&lt;/li&gt;
&lt;li&gt;In the Glitch Terminal, run &lt;code&gt;npx web-push generate-vapid-keys&lt;/code&gt;. Copy the private key
and public key values.&lt;/li&gt;
&lt;li&gt;In the Glitch UI open &lt;code&gt;.env&lt;/code&gt; and update &lt;code&gt;VAPID_PUBLIC_KEY&lt;/code&gt; and &lt;code&gt;VAPID_PRIVATE_KEY&lt;/code&gt;. Set
&lt;code&gt;VAPID_SUBJECT&lt;/code&gt; to &lt;code&gt;mailto:test@test.test&lt;/code&gt;. All of these values should be wrapped
in double quotes. After making your updates, your &lt;code&gt;.env&lt;/code&gt; file should look
similar to this:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;VAPID_PUBLIC_KEY=&quot;BKiwTvD9HA…&quot;&lt;br /&gt;VAPID_PRIVATE_KEY=&quot;4mXG9jBUaU…&quot;&lt;br /&gt;VAPID_SUBJECT=&quot;mailto:test@test.test&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Close the Glitch Terminal.&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Environment variable values (the stuff in &lt;code&gt;.env&lt;/code&gt;) are unique to a single Glitch project. If you remix your project, the values in &lt;code&gt;.env&lt;/code&gt; won&#39;t get copied over. &lt;/div&gt;&lt;/aside&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;public/index.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;VAPID_PUBLIC_KEY_VALUE_HERE&lt;/code&gt; with the value of your public key.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;register-a-service-worker&quot;&gt;Register a service worker &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#register-a-service-worker&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Your client will eventually need a service worker to receive and display
notifications. It&#39;s best to register the service worker as early as possible.
See &lt;a href=&quot;https://web.dev/push-notifications-overview/#notification&quot;&gt;Receive and display the pushed messages as
notifications&lt;/a&gt; for more context.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Replace the &lt;code&gt;// TODO add startup logic here&lt;/code&gt; comment with the following code:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// TODO add startup logic here&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;serviceWorker&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;PushManager&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./service-worker.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;serviceWorkerRegistration&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Service worker was registered.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;serviceWorkerRegistration&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;An error occurred while registering the service worker.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  subscribeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Browser does not support service workers or push messages.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;subscribeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; subscribeButtonHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;unsubscribeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; unsubscribeButtonHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;To preview the site, press &lt;strong&gt;View App&lt;/strong&gt;. Then press
&lt;strong&gt;Fullscreen&lt;/strong&gt;
&lt;img src=&quot;https://web.dev/images/glitch/fullscreen.svg&quot; alt=&quot;fullscreen&quot; style=&quot;padding: 4px 8px; opacity: .5; border: 1px solid #c3c3c3; border-radius: 5px; margin-top: 0;&quot; /&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class=&quot;aside flow color-secondary-box-text bg-secondary-box-bg&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Highlighter pen&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M10.22 9.49l-5.91 6c-.77.8-.7 2.05.08 2.85L.77 22h5.68l.74-.75c.78.81 1.95.86 2.73.05l5.96-6.05-5.66-5.76zm12.46-4l-2.82-2.87c-.78-.8-2.07-.84-2.84-.04l-5.75 5.85 5.66 5.75 5.69-5.78c.77-.81.83-2.11.06-2.91z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Key Term&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The tab that you just opened will be referred to as the &lt;strong&gt;app tab&lt;/strong&gt; throughout this codelab. &lt;/div&gt;&lt;/aside&gt;
&lt;ol&gt;
&lt;li&gt;Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Console&lt;/strong&gt; tab. You should see the message
&lt;code&gt;Service worker was registered.&lt;/code&gt; logged to the Console.&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The previous instruction assumes that you&#39;re using Google Chrome and Chrome DevTools. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;request-push-notification-permission&quot;&gt;Request push notification permission &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#request-push-notification-permission&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You should never request permission to send push notifications on page load.
Instead, your UI should ask the user if they want to receive push notifications.
Once they explicitly confirm (with a button click, for example) then you can
start the formal process for getting push notification permission from the browser.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In the Glitch UI click &lt;strong&gt;View Source&lt;/strong&gt; to return to your code.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;public/index.js&lt;/code&gt; replace the &lt;code&gt;// TODO&lt;/code&gt; comment in
&lt;code&gt;subscribeButtonHandler()&lt;/code&gt; with the following code:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// TODO&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Prevent the user from clicking the subscribe button multiple times.&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;subscribeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Notification&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestPermission&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;denied&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;The user explicitly denied the permission request.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;granted&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;The user accepted the permission request.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/ins&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Go back to the app tab and click &lt;strong&gt;Subscribe to push&lt;/strong&gt;. Your browser
or operating system will probably ask you if you want to let the website
send you push notifications. Click &lt;strong&gt;Allow&lt;/strong&gt; (or whatever equivalent phrase
your browser/OS uses). In the Console you should see a message indicating
whether the request was accepted or denied.&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; If you&#39;re in an incognito or guest window, your browser may deny the request automatically. Keep an eye out for any browser UI indicating that the request was blocked automatically. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;subscribe-to-push-notifications&quot;&gt;Subscribe to push notifications &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#subscribe-to-push-notifications&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The subscription process involves interacting with a web service controlled
by the browser vendor that&#39;s called a &lt;strong&gt;push service&lt;/strong&gt;. Once you get
the push notification subscription information you need to send it to a server
and have the server store it in a database long-term.
See &lt;a href=&quot;https://web.dev/push-notifications-overview/#subscription&quot;&gt;Subscribe the client to push notifications&lt;/a&gt;
for more context about the subscription process.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add the following highlighted code to &lt;code&gt;subscribeButtonHandler()&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;subscribeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Notification&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestPermission&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;denied&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;The user explicitly denied the permission request.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;granted&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;The user accepted the permission request.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRegistration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; subscribed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pushManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSubscription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subscribed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;User is already subscribed.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  notifyMeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  unsubscribeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; subscription &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pushManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;userVisibleOnly&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;applicationServerKey&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;urlB64ToUint8Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;VAPID_PUBLIC_KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;notifyMeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/add-subscription&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;POST&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subscription&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;userVisibleOnly&lt;/code&gt; option must be &lt;code&gt;true&lt;/code&gt;. It may one day be possible
to push messages without displaying user-visible notifications
(&lt;strong&gt;silent pushes&lt;/strong&gt;) but browsers currently don&#39;t allow that capability
because of privacy concerns.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;applicationServerKey&lt;/code&gt; value relies on a utility function that
converts a base64 string to a Uint8Array. This value is used for
authentication between your server and the push service.&lt;/p&gt;
&lt;h2 id=&quot;unsubscribe-from-push-notifications&quot;&gt;Unsubscribe from push notifications &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#unsubscribe-from-push-notifications&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After a user has subscribed to push notifications, your UI needs to
provide a way to unsubscribe in case the user changes their mind
and no longer wants to receive push notifications.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Replace the &lt;code&gt;// TODO&lt;/code&gt; comment in &lt;code&gt;unsubscribeButtonHandler()&lt;/code&gt;
with the following code:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// TODO&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRegistration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; subscription &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pushManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSubscription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/remove-subscription&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;POST&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;endpoint&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; unsubscribed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unsubscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;unsubscribed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Successfully unsubscribed from push notifications.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  unsubscribeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  subscribeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  notifyMeButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/ins&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;receive-a-push-message-and-display-it-as-a-notification&quot;&gt;Receive a push message and display it as a notification &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#receive-a-push-message-and-display-it-as-a-notification&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As mentioned before, you need a service worker to handle the
receiving and displaying of messages that were pushed to the client
from your server. See &lt;a href=&quot;https://web.dev/push-notifications-overview/#notification&quot;&gt;Receive and display the pushed messages as
notifications&lt;/a&gt; for more detail.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;public/service-worker.js&lt;/code&gt; and replace the &lt;code&gt;// TODO&lt;/code&gt; comment
in the service worker&#39;s &lt;code&gt;push&lt;/code&gt; event handler with the following code:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// TODO&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://cdn.glitch.com/614286c9-b4fc-4303-a6a9-a4cef0601b74%2Flogo.png?v=1605150951230&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;icon&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; image&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showNotification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  options&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Go back to the app tab.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Notify me&lt;/strong&gt;. You should receive a push notification.&lt;/li&gt;
&lt;li&gt;Try opening the URL of your app tab on other browsers (or even
other devices), going through the subscription workflow, and then
clicking &lt;strong&gt;Notify all&lt;/strong&gt;. You should receive the same push notification
on all of the browsers that you subscribed. Refer back to
&lt;a href=&quot;https://web.dev/push-notifications-client-codelab/#browser-compatibility&quot;&gt;Browser compatibility&lt;/a&gt; to see a list of browser/OS
combinations that are known to work or not work.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can customize the notification in lots of ways. See the parameters of
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ServiceWorkerRegistration/showNotification&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ServiceWorkerRegistration.showNotification()&lt;/code&gt;&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The call to &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting&quot;&gt;&lt;code&gt;self.skipWaiting()&lt;/code&gt;&lt;/a&gt; in your service worker&#39;s &lt;code&gt;install&lt;/code&gt; listener is important to understand. See &lt;a href=&quot;https://web.dev/service-worker-lifecycle/#skip-the-waiting-phase&quot;&gt;Skip the waiting phase&lt;/a&gt; for an explanation. Without it, the code changes that you make to your service worker wouldn&#39;t take effect immediately. You may or may not want to use this feature on your own website depending on your needs, but either way it&#39;s important to understand its effect. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;open-a-url-when-a-user-clicks-a-notification&quot;&gt;Open a URL when a user clicks a notification &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#open-a-url-when-a-user-clicks-a-notification&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the real-world, you&#39;ll probably use the notification as a way
to re-engage your user and prompt them to visit your site.
To do that, you need to configure your service worker a bit more.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Replace the &lt;code&gt;// TODO&lt;/code&gt; comment in the service worker&#39;s &lt;code&gt;notificationclick&lt;/code&gt;
event handler with the following code:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// TODO&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;notification&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitUntil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clients&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;openWindow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://web.dev&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Go back to the app tab, send yourself another notification, and then
click the notification. Your browser should open a new tab and load
&lt;code&gt;https://web.dev&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-client-codelab/#next-steps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Look at &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ServiceWorkerRegistration/showNotification&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ServiceWorkerRegistration.showNotification()&lt;/code&gt;&lt;/a&gt;
to discover all of the different ways you can customize notifications.&lt;/li&gt;
&lt;li&gt;Read &lt;a href=&quot;https://web.dev/push-notifications-overview&quot;&gt;Push notifications overview&lt;/a&gt;
for a deeper conceptual understanding of how push notifications work.&lt;/li&gt;
&lt;li&gt;Check out &lt;a href=&quot;https://web.dev/push-notifications-server-codelab/&quot;&gt;Codelab: Build a push notification server&lt;/a&gt;
to learn how to build a server that manages subscriptions and sends web push protocol
requests.&lt;/li&gt;
&lt;li&gt;Try out &lt;a href=&quot;https://tests.peter.sh/notification-generator/&quot; rel=&quot;noopener&quot;&gt;Notification Generator&lt;/a&gt;
to test out all the ways you can customize notifications.&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Kayce Basques</name>
    </author><author>
      <name>Kate Jeffreys</name>
    </author>
  </entry>
  
  <entry>
    <title>Codelab: Build a push notification server</title>
    <link href="https://web.dev/push-notifications-server-codelab/"/>
    <updated>2020-11-11T00:00:00Z</updated>
    <id>https://web.dev/push-notifications-server-codelab/</id>
    <content type="html" mode="escaped">&lt;p&gt;This codelab shows you, step-by-step, how to build a push notification server.
By the end of the codelab you&#39;ll have a server that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keeps track of push notification subscriptions (i.e. the server creates a
new database record when a client opts in to push notifications, and it
deletes an existing database record when a client opts out)&lt;/li&gt;
&lt;li&gt;Sends a push notification to a single client&lt;/li&gt;
&lt;li&gt;Sends a push notification to all subscribed clients&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This codelab is focused on helping you learn by doing and doesn&#39;t
talk about concepts much. Check out
&lt;a href=&quot;https://web.dev/push-notifications-overview/#how&quot;&gt;How do push notifications work?&lt;/a&gt;
to learn about push notification concepts.&lt;/p&gt;
&lt;p&gt;The client code of this codelab is already complete. You&#39;ll only be
implementing the server in this codelab. To learn how to implement a
push notification client, check out &lt;a href=&quot;https://web.dev/push-notifications-client-codelab&quot;&gt;Codelab: Build a push notification
client&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://push-notifications-server-codelab-complete.glitch.me/&quot; rel=&quot;noopener&quot;&gt;push-notifications-server-codelab-complete&lt;/a&gt;
(&lt;a href=&quot;https://glitch.com/edit/#!/push-notifications-server-codelab-complete&quot; rel=&quot;noopener&quot;&gt;source&lt;/a&gt;)
to see the complete code.&lt;/p&gt;
&lt;h2 id=&quot;browser-compatibility&quot;&gt;Browser compatibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-server-codelab/#browser-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This codelab is known to work with the following operating system and browser combinations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows: Chrome, Edge&lt;/li&gt;
&lt;li&gt;macOS: Chrome, Firefox&lt;/li&gt;
&lt;li&gt;Android: Chrome, Firefox&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This codelab is known to &lt;strong&gt;not&lt;/strong&gt; work with the following operating systems
(or operating system and browser combinations):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;macOS: Brave, Edge, Safari&lt;/li&gt;
&lt;li&gt;iOS&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;stack&quot;&gt;Application stack &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-server-codelab/#stack&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The server is built on top of &lt;a href=&quot;https://expressjs.com/&quot; rel=&quot;noopener&quot;&gt;Express.js&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://www.npmjs.com/package/web-push&quot; rel=&quot;noopener&quot;&gt;web-push&lt;/a&gt; Node.js library
handles all of the push notification logic.&lt;/li&gt;
&lt;li&gt;Subscription data is written to a JSON file using &lt;a href=&quot;https://www.npmjs.com/package/lowdb&quot; rel=&quot;noopener&quot;&gt;lowdb&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You don&#39;t have to use any of these technologies to implement push notifications.
We chose these technologies because they provide a reliable codelab experience.&lt;/p&gt;
&lt;h2 id=&quot;setup&quot;&gt;Setup &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-server-codelab/#setup&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;remix&quot;&gt;Get an editable copy of the code &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-server-codelab/#remix&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The code editor that you see to the right of these instructions will be called
the &lt;strong&gt;Glitch UI&lt;/strong&gt; throughout this codelab.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Remix to Edit&lt;/strong&gt; to make the project editable.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;authentication&quot;&gt;Set up authentication &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-server-codelab/#authentication&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before you can get push notifications working, you need to set up
your server and client with authentication keys.
See &lt;a href=&quot;https://web.dev/push-notifications-overview/#sign&quot;&gt;Sign your web push protocol requests&lt;/a&gt;
to learn why.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the Glitch terminal by clicking &lt;strong&gt;Tools&lt;/strong&gt; and then clicking &lt;strong&gt;Terminal&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the terminal, run &lt;code&gt;npx web-push generate-vapid-keys&lt;/code&gt;. Copy the private key
and public key values.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;.env&lt;/code&gt; and update &lt;code&gt;VAPID_PUBLIC_KEY&lt;/code&gt; and &lt;code&gt;VAPID_PRIVATE_KEY&lt;/code&gt;. Set
&lt;code&gt;VAPID_SUBJECT&lt;/code&gt; to &lt;code&gt;mailto:test@test.test&lt;/code&gt;. All of these values should be wrapped
in double quotes. After making your updates, your &lt;code&gt;.env&lt;/code&gt; file should look
similar to this:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;VAPID_PUBLIC_KEY=&quot;BKiwTvD9HA…&quot;&lt;br /&gt;VAPID_PRIVATE_KEY=&quot;4mXG9jBUaU…&quot;&lt;br /&gt;VAPID_SUBJECT=&quot;mailto:test@test.test&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Close the Glitch terminal.&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Environment variable values (the stuff in &lt;code&gt;.env&lt;/code&gt;) are unique to a single Glitch project. If you remix your project, the values in &lt;code&gt;.env&lt;/code&gt; won&#39;t get copied over. &lt;/div&gt;&lt;/aside&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;public/index.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;VAPID_PUBLIC_KEY_VALUE_HERE&lt;/code&gt; with the value of your public key.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;manage&quot;&gt;Manage subscriptions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-server-codelab/#manage&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Your client handles most of the subscription process. The main
things your server needs to do are save new push notification subscriptions
and delete old subscriptions. These subscriptions are what enable you to
push messages to clients in the future.
See &lt;a href=&quot;https://web.dev/push-notifications-overview/#subscription&quot;&gt;Subscribe the client to push notifications&lt;/a&gt;
for more context about the subscription process.&lt;/p&gt;
&lt;h3 id=&quot;save&quot;&gt;Save new subscription information &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-server-codelab/#save&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;To preview the site, press &lt;strong&gt;View App&lt;/strong&gt;. Then press
&lt;strong&gt;Fullscreen&lt;/strong&gt;
&lt;img src=&quot;https://web.dev/images/glitch/fullscreen.svg&quot; alt=&quot;fullscreen&quot; style=&quot;padding: 4px 8px; opacity: .5; border: 1px solid #c3c3c3; border-radius: 5px; margin-top: 0;&quot; /&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;aside class=&quot;aside flow color-secondary-box-text bg-secondary-box-bg&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Highlighter pen&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M10.22 9.49l-5.91 6c-.77.8-.7 2.05.08 2.85L.77 22h5.68l.74-.75c.78.81 1.95.86 2.73.05l5.96-6.05-5.66-5.76zm12.46-4l-2.82-2.87c-.78-.8-2.07-.84-2.84-.04l-5.75 5.85 5.66 5.75 5.69-5.78c.77-.81.83-2.11.06-2.91z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Key Term&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; We&#39;ll refer to the tab that you just opened as the &lt;strong&gt;app tab&lt;/strong&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Register service worker&lt;/strong&gt; in the app tab. In the status box you
should see a message similar to this:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Service worker registered. Scope: https://desert-cactus-sunset.glitch.me/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;In the app tab click &lt;strong&gt;Subscribe to push&lt;/strong&gt;. Your browser or operating system will probably
ask you if you want to let the website send you push notifications. Click &lt;strong&gt;Allow&lt;/strong&gt; (or whatever
equivalent phrase your browser/OS uses). In the status box you should see a message similar
to this:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Service worker subscribed to push.  Endpoint: https://fcm.googleapis.com/fcm/send/…&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The endpoint URL changes depending on what browser you&#39;re using. For example, on Firefox the URL starts with &lt;code&gt;https://updates.push.services.mozilla.com/…&lt;/code&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;ol&gt;
&lt;li&gt;Go back to your code by clicking &lt;strong&gt;View Source&lt;/strong&gt; in the Glitch UI.&lt;/li&gt;
&lt;li&gt;Open the Glitch Logs by clicking &lt;strong&gt;Tools&lt;/strong&gt; and then clicking &lt;strong&gt;Logs&lt;/strong&gt;. You
should see &lt;code&gt;/add-subscription&lt;/code&gt; followed by some data. &lt;code&gt;/add-subscription&lt;/code&gt; is
the URL that the client sends a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Methods/POST&quot; rel=&quot;noopener&quot;&gt;POST&lt;/a&gt;
request to when it wants to subscribe to push notifications. The data that
follows is the client&#39;s subscription information that you need to save.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;server.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Update the &lt;code&gt;/add-subscription&lt;/code&gt; route handler logic with the following code:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/add-subscription&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/add-subscription&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Subscribing &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;endpoint&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;subscriptions&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The database writes to &lt;code&gt;.data/db.json&lt;/code&gt;. To inspect this file in Glitch, click &lt;strong&gt;Tools&lt;/strong&gt;, then click &lt;strong&gt;Terminal&lt;/strong&gt;, then run &lt;code&gt;cat .data/db.json&lt;/code&gt; in the Terminal. &lt;code&gt;.data/db.json&lt;/code&gt; is deleted every time that you edit your app. This is because Glitch runs the &lt;code&gt;start&lt;/code&gt; script in &lt;code&gt;package.json&lt;/code&gt; every time you edit your app, and that script includes a call to &lt;code&gt;rm .data/db.json&lt;/code&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;delete-old-subscription-information&quot;&gt;Delete old subscription information &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-server-codelab/#delete-old-subscription-information&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Go back to the app tab.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Unsubscribe from push&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Look at the Glitch Logs again. You should see &lt;code&gt;/remove-subscription&lt;/code&gt; followed
by the client&#39;s subscription information.&lt;/li&gt;
&lt;li&gt;Update the &lt;code&gt;/remove-subscription&lt;/code&gt; route handler logic with the following code:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/remove-subscription&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/remove-subscription&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Unsubscribing &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;endpoint&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;subscriptions&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;endpoint&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;send-notifications&quot;&gt;Send notifications &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-server-codelab/#send-notifications&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As explained in &lt;a href=&quot;https://web.dev/push-notifications-overview/#send&quot;&gt;Send a push message&lt;/a&gt;,
your server doesn&#39;t actually send the push messages directly to clients.
Rather, it relies on a push service to do that. Your server basically
just kicks off the process of pushing messages to clients by making web
service requests (web push protocol requests) to a web service (the push service)
owned by the browser vendor that your user uses.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update the &lt;code&gt;/notify-me&lt;/code&gt; route handler logic with the following code:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/notify-me&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/notify-me&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Notifying &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;endpoint&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; subscription &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;      db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;subscriptions&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;endpoint&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token function&quot;&gt;sendNotifications&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;subscription&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Update the &lt;code&gt;sendNotifications()&lt;/code&gt; function with the following code:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendNotifications&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;subscriptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// TODO&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// Create the notification content.&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; notification &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, Notifications!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;ID: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;floor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// Customize how the push service should attempt to deliver the push message.&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// And provide authentication information.&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token constant&quot;&gt;TTL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;vapidDetails&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; vapidDetails&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token comment&quot;&gt;// Send a push message to each client specified in the subscriptions array.&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  subscriptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;subscription&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; endpoint &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;endpoint&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; endpoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;endpoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; endpoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    webpush&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendNotification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subscription&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; notification&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Endpoint ID: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;id&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Result: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;statusCode&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Endpoint ID: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;id&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Error: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;error&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Update the &lt;code&gt;/notify-all&lt;/code&gt; route handler logic with the following code:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/notify-all&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/notify-all&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Notifying all subscribers&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; subscriptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;      db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;subscriptions&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cloneDeep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subscriptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token function&quot;&gt;sendNotifications&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subscriptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;409&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Go back to the app tab.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Unsubscribe from push&lt;/strong&gt; and then click &lt;strong&gt;Subscribe to push&lt;/strong&gt; again.
This is only necessary because, as mentioned before, Glitch restarts the project
every time you edit the code and the project is configured to delete the database on startup.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Notify me&lt;/strong&gt;. You should receive a push notification. The title should
be &lt;code&gt;Hello, Notifications!&lt;/code&gt; and the body should be &lt;code&gt;ID: &amp;lt;ID&amp;gt;&lt;/code&gt; where &lt;code&gt;&amp;lt;ID&amp;gt;&lt;/code&gt; is a
random number.&lt;/li&gt;
&lt;li&gt;Open your app on other browsers or devices and try subscribing them to push notifications
and then clicking the &lt;strong&gt;Notify all&lt;/strong&gt; button. You should receive the same notification on
all of your subscribed devices (i.e. the ID in the body of the push notification should
be the same).&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-server-codelab/#next-steps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Read &lt;a href=&quot;https://web.dev/push-notifications-overview&quot;&gt;Push notifications overview&lt;/a&gt;
for a deeper conceptual understanding of how push notifications work.&lt;/li&gt;
&lt;li&gt;Check out &lt;a href=&quot;https://web.dev/push-notifications-client-codelab/&quot;&gt;Codelab: Build a push notification client&lt;/a&gt;
to learn how to build a client that requests notification permission, subscribes
the device to receive push notifications, and uses a service worker to receive
push messages and display the messages as notifications.&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Kayce Basques</name>
    </author><author>
      <name>Kate Jeffreys</name>
    </author>
  </entry>
  
  <entry>
    <title>Push notifications overview</title>
    <link href="https://web.dev/push-notifications-overview/"/>
    <updated>2020-11-10T00:00:00Z</updated>
    <id>https://web.dev/push-notifications-overview/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;what&quot;&gt;What are push notifications? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#what&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Push messages enable you to bring information to the attention of your
users even when they&#39;re not using your website. They&#39;re called &lt;strong&gt;push&lt;/strong&gt;
messages because you can &amp;quot;push&amp;quot; information to your users even when they&#39;re
not active. Compare &lt;a href=&quot;https://en.wikipedia.org/wiki/Push_technology&quot; rel=&quot;noopener&quot;&gt;Push
technology&lt;/a&gt; with &lt;a href=&quot;https://en.wikipedia.org/wiki/Pull_technology&quot; rel=&quot;noopener&quot;&gt;Pull
technology&lt;/a&gt; to understand this
concept further.&lt;/p&gt;
&lt;p&gt;Notifications present small chunks of information to a user. Websites can use
notifications to tell users about important, time-sensitive events, or actions
the user needs to take. The look and feel of notifications varies between platforms:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Examples of notifications on macOS and Android.&quot; decoding=&quot;async&quot; height=&quot;361&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yC2ZZRuLFnnYGGmPUu9h.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Examples of notifications on macOS and Android.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Push messages and notifications are two separate but complementary technologies.
Push is the technology for sending messages from your server to users even when
they&#39;re not actively using your website. Notifications is the technology for
displaying the pushed information on the user&#39;s device. It&#39;s possible to use
notifications without push messaging. One day it may also be possible to use
push messages without user-facing notifications (&lt;strong&gt;silent push&lt;/strong&gt;) but browsers
currently don&#39;t allow that. In practice they&#39;re usually used together.
A non-technical user probably won&#39;t understand the difference between push
messages and notifications. In this collection when
we say &lt;strong&gt;push notifications&lt;/strong&gt; we mean the combination of pushing a message
followed by displaying it as a notification. When we say &lt;strong&gt;push messages&lt;/strong&gt;
we are referring to push technology on its own. And when we say &lt;strong&gt;notifications&lt;/strong&gt;
we&#39;re referring to notification technology on its own.&lt;/p&gt;
&lt;h2 id=&quot;why&quot;&gt;Why use push notifications? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#why&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;For users, push notifications are a way to receive &lt;strong&gt;timely&lt;/strong&gt;, &lt;strong&gt;relevant&lt;/strong&gt;,
and &lt;strong&gt;precise&lt;/strong&gt; information.&lt;/li&gt;
&lt;li&gt;For you (a website owner), push notifications are a way to increase user
engagement.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; If you try to use push notifications for content that your users don&#39;t find timely, relevant, and precise, you&#39;ll probably end up annoying your users and reducing overall engagement. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;how&quot;&gt;How do push notifications work? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#how&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At a high-level, the key steps for implementing push notifications are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Adding client logic to ask the user for permission to send push notifications, and
then sending client identifier information to your server for storage in a database.&lt;/li&gt;
&lt;li&gt;Adding server logic to push messages to client devices.&lt;/li&gt;
&lt;li&gt;Adding client logic to receive messages that have been pushed to the device
and displaying them as notifications.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The rest of this page explains these steps in more detail.&lt;/p&gt;
&lt;h3 id=&quot;permission&quot;&gt;Get permission to send push notifications &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#permission&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First, your website needs to get the user&#39;s permission to send push notifications.
This should be triggered by a user gesture, such as clicking a &lt;strong&gt;Yes&lt;/strong&gt; button
next to a &lt;code&gt;Do you want to receive push notifications?&lt;/code&gt; prompt. After that confirmation,
call &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Notification/requestPermission&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Notification.requestPermission()&lt;/code&gt;&lt;/a&gt;. The operating system or
browser on the user&#39;s device will probably present some kind of UI to formally confirm that the
user wants to opt in to push notifications. This UI varies across platforms.&lt;/p&gt;
&lt;h3 id=&quot;subscription&quot;&gt;Subscribe the client to push notifications &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#subscription&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After you get permission, your website needs to initiate the process of
subscribing the user to push notifications. This is done through JavaScript,
using the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Push_API&quot; rel=&quot;noopener&quot;&gt;Push API&lt;/a&gt;. You&#39;ll need to provide a public authentication key
during the subscription process, which you&#39;ll learn more about later. After
you kick off the subscription process, the browser makes a network request
to a web service known as a push service, which you&#39;ll also learn more about later.&lt;/p&gt;
&lt;p&gt;Assuming that the subscription was successful, the browser returns a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/PushSubscription&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;PushSubscription&lt;/code&gt;&lt;/a&gt;
object. You&#39;ll need to store this data long-term.
Usually this is done by sending the information to a server that you control,
and then having the server store it in a database.&lt;/p&gt;
&lt;img alt=&quot;Get permission to send push messages. Get PushSubscription. Send PushSubscription to your server.&quot; decoding=&quot;async&quot; height=&quot;213&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/F7gyVwzozw0eYkbTBChu.svg&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;send&quot;&gt;Send a push message &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#send&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Your server doesn&#39;t actually send the push message directly to a client. A
&lt;strong&gt;push service&lt;/strong&gt; does that. A push service is a web service controlled by your
user&#39;s browser vendor. When you want to send a push notification to a client you need
to make a web service request to a push service. The web service request that
you send to the push service is known as a &lt;strong&gt;web push protocol request&lt;/strong&gt;. The
web push protocol request should include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What data to include in the message.&lt;/li&gt;
&lt;li&gt;What client to send the message to.&lt;/li&gt;
&lt;li&gt;Instructions on how the push service should deliver the message. For example, you
can specify that the push service should stop attempting to send the message
after 10 minutes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Normally you make the web push protocol request through a server that you control.
Of course, your server doesn&#39;t have to construct the raw web service request
itself. There are libraries that can handle that for you, such as the
&lt;a href=&quot;https://github.com/web-push-libs/&quot; rel=&quot;noopener&quot;&gt;web-push-libs&lt;/a&gt;. But the underlying mechanism is
a web service request over HTTP.&lt;/p&gt;
&lt;img alt=&quot;Your server sends a web push protocol request to the push service and the push service sends to the message to the user&amp;#x27;s device.&quot; decoding=&quot;async&quot; height=&quot;220&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BD7hUXHhprQfUgWWGsMk.svg&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;The push service receives your request, authenticates it, and routes the push
message to the appropriate client. If the client&#39;s browser is offline, the push
service queues the push message until the browser comes online.&lt;/p&gt;
&lt;p&gt;Each browser uses whatever push service it wants. You as a website developer
have no control over that. This isn&#39;t a problem because the web push protocol
request is &lt;a href=&quot;https://tools.ietf.org/html/draft-ietf-webpush-protocol&quot; rel=&quot;noopener&quot;&gt;standardized&lt;/a&gt;.
In other words, you don&#39;t have to care which push service the browser vendor is
using. You just need to make sure that your web push protocol request follows the spec.
Among other things, the spec states that the request must include certain headers
and the data must be sent as a stream of bytes.&lt;/p&gt;
&lt;p&gt;You do, however, need to make sure that you&#39;re sending the web push protocol
request to the correct push service. The &lt;code&gt;PushSubscription&lt;/code&gt; data that the
browser returned to you during the subscription process provides this
information. A &lt;code&gt;PushSubscription&lt;/code&gt; object looks like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;endpoint&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://fcm.googleapis.com/fcm/send/c1KrmpTuRm…&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;expirationTime&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;keys&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;p256dh&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;BGyyVt9FFV…&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;auth&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;R9sidzkcdf…&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The domain of the &lt;code&gt;endpoint&lt;/code&gt; is essentially the push service. The path of the
&lt;code&gt;endpoint&lt;/code&gt; is client identifier information that helps the push service determine
exactly which client to push the message to.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;keys&lt;/code&gt; are used for encryption, which is explained next.&lt;/p&gt;
&lt;h4 id=&quot;encrypt&quot;&gt;Encrypt the push message &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#encrypt&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The data that you send to a push service must be encrypted. This prevents
the push service from being able to view the data you&#39;re sending to the client.
Remember that the browser vendor decides what push service to use, and that
push service could theoretically be unsafe or insecure. Your server must use
the &lt;code&gt;keys&lt;/code&gt; provided in the &lt;code&gt;PushSubscription&lt;/code&gt; to encrypt its web push protocol
requests.&lt;/p&gt;
&lt;h4 id=&quot;sign&quot;&gt;Sign your web push protocol requests &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#sign&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The push service provides a way to prevent anyone else from sending messages to your
users. Technically you don&#39;t have to do this but the easiest implementation on
Chrome requires it. It&#39;s optional on Firefox. Other browsers may require it
in the future.&lt;/p&gt;
&lt;p&gt;This workflow involves a private key and public key that are unique to your
application. The authentication process roughly works like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You generate the private and public key as a one-off task. The combination
of the private and public key is known as the
&lt;strong&gt;application server keys&lt;/strong&gt;. You might also see them called the &lt;strong&gt;VAPID
keys&lt;/strong&gt;. &lt;a href=&quot;https://tools.ietf.org/html/draft-thomson-webpush-vapid-02&quot; rel=&quot;noopener&quot;&gt;VAPID&lt;/a&gt; is
the spec that defines this authentication process.&lt;/li&gt;
&lt;li&gt;When you subscribe a client to push notifications from your JavaScript code,
you provide your public key. When the push service generates an &lt;code&gt;endpoint&lt;/code&gt;
for the device, it associates the provided public key with the &lt;code&gt;endpoint&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When you send a web push protocol request, you sign some JSON information
with your private key.&lt;/li&gt;
&lt;li&gt;When the push service receives your web push protocol request, it uses the stored
public key to authenticate the signed information. If the signature is valid
then the push service knows that the request came from a server with the
matching private key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;customize&quot;&gt;Customize the delivery of the push message &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#customize&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The web push protocol request spec also defines parameters that let you
customize how the push service attempts to send the push message to the client.
For example, you can customize:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Time-To-Live (TTL) of a message, which defines how long the push service should
attempt to deliver a message.&lt;/li&gt;
&lt;li&gt;The urgency of the message, which is useful in case the push service is preserving
the client&#39;s battery life by only delivering high-priority messages.&lt;/li&gt;
&lt;li&gt;The topic of a message, which replaces any pending messages of the same topic
with the latest message.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;notification&quot;&gt;Receive and display the pushed messages as notifications &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#notification&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once you&#39;ve sent the web push protocol request to the push service, the push service keeps
your request queued until one of the following events happens:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The client comes online and the push service delivers the push message.&lt;/li&gt;
&lt;li&gt;The message expires.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When a client browser receives a pushed message, it decrypts the push message
data and dispatches a &lt;code&gt;push&lt;/code&gt; event to your &lt;a href=&quot;https://web.dev/service-workers-cache-storage/#service-workers&quot;&gt;service
worker&lt;/a&gt;. A service worker is
basically JavaScript code that can run in the background, even when your website
isn&#39;t open or the browser is closed. In your service worker&#39;s &lt;code&gt;push&lt;/code&gt; event
handler you call &lt;code&gt;ServiceWorkerRegistration.showNotification()&lt;/code&gt; to display the information
as a notification.&lt;/p&gt;
&lt;img alt=&quot;Message arrives on device. Browser wakes up service worker. Push event is dispatched.&quot; decoding=&quot;async&quot; height=&quot;238&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2pLZ4T0vVrG3nqitaAeH.svg&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#where-to-go-next&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Web Push Notification Overview&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-how-push-works/&quot;&gt;How Push Works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-subscribing-a-user/&quot;&gt;Subscribing a User&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-permissions-ux/&quot;&gt;Permission UX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/sending-messages-with-web-push-libraries/&quot;&gt;Sending Messages with Web Push Libraries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-web-push-protocol/&quot;&gt;Web Push Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-handling-messages/&quot;&gt;Handling Push Events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-display-a-notification/&quot;&gt;Displaying a Notification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-notification-behaviour/&quot;&gt;Notification Behavior&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-common-notification-patterns/&quot;&gt;Common Notification Patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-faq/&quot;&gt;Push Notifications FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-common-issues-and-reporting-bugs/&quot;&gt;Common Issues and Reporting Bugs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;code-labs&quot;&gt;Code labs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-overview/#code-labs&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-client-codelab/&quot;&gt;Build a push notification client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-server-codelab/&quot;&gt;Build a push notification server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Kayce Basques</name>
    </author><author>
      <name>Matt Gaunt</name>
    </author>
  </entry>
  
  <entry>
    <title>How to create high-performance CSS animations</title>
    <link href="https://web.dev/animations-guide/"/>
    <updated>2020-10-06T00:00:00Z</updated>
    <id>https://web.dev/animations-guide/</id>
    <content type="html" mode="escaped">&lt;p&gt;This guide teaches you how to create high-performance CSS animations.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://web.dev/animations-overview/&quot;&gt;Why are some animations slow?&lt;/a&gt; to learn the theory behind
these recommendations.&lt;/p&gt;
&lt;h2 id=&quot;browser-compatibility&quot;&gt;Browser compatibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#browser-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All of the CSS properties that this guide recommends have good cross-browser support.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/transform#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;transform&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/opacity#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;opacity&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/will-change#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;will-change&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;move&quot;&gt;Move an element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#move&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To move an element, use the &lt;code&gt;translate&lt;/code&gt; or &lt;code&gt;rotation&lt;/code&gt; keyword values of the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/transform&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;transform&lt;/code&gt;&lt;/a&gt; property.&lt;/p&gt;
&lt;p&gt;For example to slide an item into view, use &lt;code&gt;translate&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.animate&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; slide-in 0.7s both&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; slide-in&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;0%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;-1000px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;100%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/animation-slide-in?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;animation-slide-in on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Items can also be rotated, in the example below 360 degrees.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.animate&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; rotate 0.7s ease-in-out both&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; rotate&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;0%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;100%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rotate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;360deg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/animation-rotate?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;animation-rotate on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;resize&quot;&gt;Resize an element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#resize&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To resize an element, use the &lt;code&gt;scale&lt;/code&gt; keyword value of the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/transform&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;transform&lt;/code&gt;&lt;/a&gt; property.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.animate&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; scale 1.5s both&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; scale&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;50%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0.5&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;100%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;scale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/animation-scale?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;animation-scale on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;visibility&quot;&gt;Change an element&#39;s visibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#visibility&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To show or hide an element, use &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/opacity&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;opacity&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.animate&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; opacity 2.5s both&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; opacity&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;0%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;50%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;100%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/animation-opacity?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;animation-opacity on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Find copy and paste examples of various animations at &lt;a href=&quot;https://animista.net/&quot;&gt;Animista&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;triggers&quot;&gt;Avoid properties that trigger layout or paint &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#triggers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before using any CSS property for animation (other than &lt;code&gt;transform&lt;/code&gt; and &lt;code&gt;opacity&lt;/code&gt;),
determine the property&#39;s impact on the &lt;a href=&quot;https://web.dev/animations-overview/#pipeline&quot;&gt;rendering pipeline&lt;/a&gt;.
Avoid any property that triggers layout or paint unless absolutely necessary.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; If you must use a property that triggers layout or paint, it is unlikely that you will be able to make the animation smooth and high-performance. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;force&quot;&gt;Force layer creation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#force&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As explained in &lt;a href=&quot;https://web.dev/animations-overview&quot;&gt;Why are some animations slow?&lt;/a&gt;,
by placing elements on a new layer they can be repainted without also requiring the rest of the layout to be repainted.&lt;/p&gt;
&lt;p&gt;Browsers will often make good decisions about which items should be placed on a new layer,
but you can manually force layer creation with the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/will-change&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;will-change&lt;/code&gt;&lt;/a&gt; property.
As the name suggests, this property tells the browser that this element is going to be changed in some way.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; As layer creation can cause other performance issues, this property should not be used as a premature optimization. Instead, you should only use it when you are seeing jank and think that promoting the element to a new layer may help. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;In CSS this property can be applied to any selector:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;body &gt; .sidebar&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;will-change&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; transform&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;However, &lt;a href=&quot;https://drafts.csswg.org/css-will-change/&quot; rel=&quot;noopener&quot;&gt;the specification&lt;/a&gt;
suggests this approach should only be taken for elements that are always about to change.
If the above example was a sidebar the user could slide in and out, that might be the case.
Some items on your page may not frequently change,
and so it would be better to apply &lt;code&gt;will-change&lt;/code&gt; using JavaScript
at a point where it becomes likely the change will occur.
You&#39;ll need to make sure to give the browser enough time to perform the optimizations needed
and then remove the property once the changing has stopped.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; For more information and examples of correct use of &lt;code&gt;will-change&lt;/code&gt; read &lt;a href=&quot;https://dev.opera.com/articles/css-will-change-property/&quot;&gt;Everything You Need To Know About The CSS &lt;code&gt;will-change&lt;/code&gt; Property&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;If you need a way to force layer creation in one of the rare browsers that doesn&#39;t support
&lt;code&gt;will-change&lt;/code&gt; (most likely Internet Explorer at this point),
you can set &lt;code&gt;transform: translateZ(0)&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;debug&quot;&gt;Debug slow or janky animations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#debug&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Chrome DevTools and Firefox DevTools have lots of tools to help you figure out why
your animations are slow or janky.&lt;/p&gt;
&lt;h3 id=&quot;layout&quot;&gt;Check if an animation triggers layout &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#layout&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An animation that moves an element using something other than &lt;code&gt;transform&lt;/code&gt;, is likely to be slow.
In the following example, I have achieved the same visual result animating &lt;code&gt;top&lt;/code&gt; and &lt;code&gt;left&lt;/code&gt;, and using &lt;code&gt;transform&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;worse&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Don&#39;t&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.box&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; move 3s ease infinite&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; move&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token selector&quot;&gt;50%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;     &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;90vh - 160px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;     &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;90vw - 200px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;better&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Do&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.box&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; move 3s ease infinite&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@keyframes&lt;/span&gt; move&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token selector&quot;&gt;50%&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;     &lt;span class=&quot;token property&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;90vw - 200px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;90vh - 160px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;p&gt;You can test this in the following two Glitch examples,
and explore performance using DevTools.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://glitch.com/~animation-with-top-left&quot; rel=&quot;noopener&quot;&gt;Before&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://glitch.com/~animation-with-transform&quot; rel=&quot;noopener&quot;&gt;After&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;layout-chrome&quot;&gt;Chrome DevTools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#layout-chrome&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Open the &lt;strong&gt;Performance&lt;/strong&gt; panel.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#record-runtime&quot; rel=&quot;noopener&quot;&gt;Record runtime performance&lt;/a&gt;
while your animation is happening.&lt;/li&gt;
&lt;li&gt;Inspect the &lt;strong&gt;Summary&lt;/strong&gt; tab.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you see a nonzero value for &lt;strong&gt;Rendering&lt;/strong&gt; in the &lt;strong&gt;Summary&lt;/strong&gt; tab, it may mean that your
animation is causing the browser to do layout work.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Summary panel shows 37ms for rendering and 79ms for painting.&quot; decoding=&quot;async&quot; height=&quot;699&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/cMNQR2jBEwa6ku5POXtZ.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The &lt;a href=&quot;https://animation-with-top-left.glitch.me/&quot;&gt;animation-with-top-left&lt;/a&gt;
    example causes rendering work.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Summary panel show zero values for rendering and painting.&quot; decoding=&quot;async&quot; height=&quot;639&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/3bn44P9h6lR93uBNRXY3.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The &lt;a href=&quot;https://animation-with-transform.glitch.me/&quot;&gt;animation-with-transform&lt;/a&gt;
    example does not cause rendering work.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id=&quot;layout-firefox&quot;&gt;Firefox DevTools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#layout-firefox&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;In Firefox DevTools the &lt;a href=&quot;https://developer.mozilla.org/docs/Tools/Performance/Waterfall&quot; rel=&quot;noopener&quot;&gt;Waterfall&lt;/a&gt;
can help you to understand where the browser is spending time.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the &lt;strong&gt;Performance&lt;/strong&gt; panel.&lt;/li&gt;
&lt;li&gt;In the panel Start Recording Performance while your animation is happening.&lt;/li&gt;
&lt;li&gt;Stop the recording and inspect the &lt;strong&gt;Waterfall&lt;/strong&gt; tab.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you see entries for &lt;a href=&quot;https://developer.mozilla.org/docs/Tools/Performance/Scenarios/Animating_CSS_properties&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Recalculate Style&lt;/strong&gt;&lt;/a&gt;
then the browser is having to begin at the start of the &lt;a href=&quot;https://developer.mozilla.org/docs/Tools/Performance/Scenarios/Animating_CSS_properties&quot; rel=&quot;noopener&quot;&gt;rendering waterfall&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;fps&quot;&gt;Check if an animation is dropping frames &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#fps&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Open the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#rendering&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Rendering&lt;/strong&gt; tab&lt;/a&gt; of Chrome DevTools.&lt;/li&gt;
&lt;li&gt;Enable the &lt;strong&gt;FPS meter&lt;/strong&gt; checkbox.&lt;/li&gt;
&lt;li&gt;Watch the values as your animation runs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At the top of the &lt;strong&gt;FPS meter&lt;/strong&gt; UI you see the label &lt;strong&gt;Frames&lt;/strong&gt;. Below
that you see a value along the lines of &lt;code&gt;50% 1 (938 m) dropped of 1878&lt;/code&gt;.
A high-performance animation will have a high percentage, e.g. &lt;code&gt;99%&lt;/code&gt;. A
high percentage means that few frames are being dropped and the animation will look smooth.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The fps meter shows 50% of frames were dropped&quot; decoding=&quot;async&quot; height=&quot;469&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 710px) 710px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/i9Cg7nswyO7jB768kpdQ.jpg?auto=format&amp;w=1420 1420w&quot; width=&quot;710&quot; /&gt;
  &lt;figcaption&gt;
    The &lt;a href=&quot;https://animation-with-top-left.glitch.me/&quot;&gt;animation-with-top-left&lt;/a&gt;
    example causes 50% of frames to be dropped
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The fps meter shows only 1% of frames were dropped&quot; decoding=&quot;async&quot; height=&quot;468&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 710px) 710px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/FGROZ0i15tCAoiIOoEdG.jpg?auto=format&amp;w=1420 1420w&quot; width=&quot;710&quot; /&gt;
  &lt;figcaption&gt;
    The &lt;a href=&quot;https://animation-with-transform.glitch.me/&quot;&gt;animation-with-transform&lt;/a&gt;
    example causes only 1% of frames to be dropped.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;paint&quot;&gt;Check if an animation triggers paint &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#paint&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When it comes to painting, some things are more expensive than others.
For example, anything that involves a blur (like a shadow, for example) is going to take longer to paint than drawing a red box.
In terms of CSS, however, this isn&#39;t always obvious:
&lt;code&gt;background: red;&lt;/code&gt; and &lt;code&gt;box-shadow: 0, 4px, 4px, rgba(0,0,0,0.5);&lt;/code&gt;
don&#39;t necessarily look like they have vastly different performance characteristics, but they do.&lt;/p&gt;
&lt;p&gt;Browser DevTools can help you to identify which areas need to be repainted,
and performance issues related to painting.&lt;/p&gt;
&lt;h4 id=&quot;paint-chrome&quot;&gt;Chrome DevTools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#paint-chrome&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Open the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#rendering&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Rendering&lt;/strong&gt; tab&lt;/a&gt; of Chrome DevTools.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Paint Flashing&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Move the pointer around the screen.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A UI element highlighted in green to demonstrate it will be repainted&quot; decoding=&quot;async&quot; height=&quot;185&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 708px) 708px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/MzAeQc5PvCltcm3gWaNV.jpg?auto=format&amp;w=1416 1416w&quot; width=&quot;708&quot; /&gt;
  &lt;figcaption&gt;In this example from Google Maps you can see the elements that will be repainted.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you see the whole screen flashing,
or areas that you don&#39;t think should change highlighted then you can do some investigation.&lt;/p&gt;
&lt;p&gt;If you need to dig into whether a particular property is causing performance issues due to painting,
the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/evaluate-performance/reference/#paint-profiler&quot; rel=&quot;noopener&quot;&gt;paint profiler&lt;/a&gt;
in Chrome DevTools can help.&lt;/p&gt;
&lt;h4 id=&quot;paint-firefox&quot;&gt;Firefox DevTools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#paint-firefox&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Settings&lt;/strong&gt; and add a Toolbox button for &lt;a href=&quot;https://developer.mozilla.org/docs/Tools/Paint_Flashing_Tool&quot; rel=&quot;noopener&quot;&gt;Toggle paint flashing&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;On the page you want to inspect, toggle the button on and move your mouse or scroll to see highlighted areas.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/animations-guide/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Where possible restrict animations to &lt;code&gt;opacity&lt;/code&gt; and &lt;code&gt;transform&lt;/code&gt;
in order to keep animations on the compositing stage of the rendering path.
Use DevTools to check which stage of the path is being affected by your animations.&lt;/p&gt;
&lt;p&gt;Use the paint profiler to see if any paint operations are particularly expensive.
If you find anything,
see if a different CSS property will give the same look and feel with better performance.&lt;/p&gt;
&lt;p&gt;Use the &lt;code&gt;will-change&lt;/code&gt; property sparingly,
and only if you encounter a performance issue.&lt;/p&gt;
</content>
    <author>
      <name>Rachel Andrew</name>
    </author><author>
      <name>Kayce Basques</name>
    </author>
  </entry>
  
  <entry>
    <title>Add an Apple touch icon to your Progressive Web App</title>
    <link href="https://web.dev/codelab-apple-touch-icon/"/>
    <updated>2019-08-26T00:00:00Z</updated>
    <id>https://web.dev/codelab-apple-touch-icon/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This codelab uses Chrome DevTools. &lt;a href=&quot;https://www.google.com/chrome&quot;&gt;Download Chrome&lt;/a&gt; if you don&#39;t already have it. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Safari for iOS users can manually add &lt;a href=&quot;https://web.dev/discover-installable&quot;&gt;Progressive Web Apps (PWAs)&lt;/a&gt; to
their home screen. The icon that appears on the iOS home screen when a PWA is added is called
the &lt;em&gt;Apple touch icon&lt;/em&gt;. This codelab shows you how to add an Apple touch icon to a PWA. It assumes
that you have access to an iOS device.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; iOS Safari users can add any webpage to their home screen. It doesn&#39;t have to be a PWA. In fact, the example app used in this codelab isn&#39;t a PWA.  But in most cases a PWA would be the kind of app that a user would most likely want to add to their home screen. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;measure&quot;&gt;Measure &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-apple-touch-icon/#measure&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Open the example app in a new tab:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;strong&gt;Remix to Edit&lt;/strong&gt; to make the project editable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To preview the site, press &lt;strong&gt;View App&lt;/strong&gt;. Then press
&lt;strong&gt;Fullscreen&lt;/strong&gt;
&lt;img src=&quot;https://web.dev/images/glitch/fullscreen.svg&quot; alt=&quot;fullscreen&quot; style=&quot;padding: 4px 8px; opacity: .5; border: 1px solid #c3c3c3; border-radius: 5px; margin-top: 0;&quot; /&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Note the URL of your example app. It&#39;ll be something like &lt;code&gt;https://example.glitch.me&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Run a &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/pwa/&quot; rel=&quot;noopener&quot;&gt;Lighthouse PWA audit&lt;/a&gt; on your example app in Chrome DevTools:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Lighthouse&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Make sure the &lt;strong&gt;Progressive Web App&lt;/strong&gt; checkbox is selected in the &lt;em&gt;Categories&lt;/em&gt; list.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Generate report&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the &lt;strong&gt;PWA Optimized&lt;/strong&gt; section, Lighthouse reports that the example app &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/pwa/apple-touch-icon/&quot; rel=&quot;noopener&quot;&gt;doesn&#39;t provide a valid
Apple touch icon&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Does not provide a valid apple-touch-icon&quot; decoding=&quot;async&quot; height=&quot;283&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MTeobnXovn2UGJW1lhQ9.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The &lt;b&gt;Does not provide a valid apple-touch-icon&lt;/b&gt; audit
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;add-the-example-app-to-an-ios-home-screen&quot;&gt;Add the example app to an iOS home screen &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-apple-touch-icon/#add-the-example-app-to-an-ios-home-screen&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To demonstrate how an Apple touch icon provides a more polished user experience, first try adding
the example app to your iOS device&#39;s home screen when an Apple touch icon hasn&#39;t been specified.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open Safari for iOS.&lt;/li&gt;
&lt;li&gt;Open the URL of your example app. This is the URL like &lt;code&gt;https://example.glitch.me&lt;/code&gt; that you
noted earlier.&lt;/li&gt;
&lt;li&gt;Tap &lt;strong&gt;Share&lt;/strong&gt; &lt;img alt=&quot;Apple Share Button&quot; decoding=&quot;async&quot; height=&quot;60&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 46px) 46px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/79A8XsdBaHeLGY5Wdm1J.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/79A8XsdBaHeLGY5Wdm1J.png?auto=format&amp;w=46 46w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/79A8XsdBaHeLGY5Wdm1J.png?auto=format&amp;w=52 52w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/79A8XsdBaHeLGY5Wdm1J.png?auto=format&amp;w=60 60w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/79A8XsdBaHeLGY5Wdm1J.png?auto=format&amp;w=68 68w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/79A8XsdBaHeLGY5Wdm1J.png?auto=format&amp;w=78 78w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/79A8XsdBaHeLGY5Wdm1J.png?auto=format&amp;w=89 89w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/79A8XsdBaHeLGY5Wdm1J.png?auto=format&amp;w=92 92w&quot; style=&quot;height:1.2em;width:0.92em;vertical-align:top;&quot; width=&quot;46&quot; /&gt;  &amp;gt;
&lt;strong&gt;Add to Home Screen&lt;/strong&gt;. You&#39;ll probably have to swipe left to see this option.&lt;/li&gt;
&lt;li&gt;Tap &lt;strong&gt;Add&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because the site hasn&#39;t specified an Apple touch icon, iOS just generates an icon for the site
from the page&#39;s content.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;An auto-generated home screen icon.&quot; decoding=&quot;async&quot; height=&quot;1136&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 640px) 640px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mjkYYf7Fjpm4EwzMJ7Xc.png?auto=format&amp;w=1280 1280w&quot; width=&quot;640&quot; /&gt;
  &lt;figcaption&gt;
    An auto-generated home screen icon.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;add-an-apple-touch-icon-to-the-example-app&quot;&gt;Add an Apple touch icon to the example app &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-apple-touch-icon/#add-an-apple-touch-icon-to-the-example-app&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Uncomment the &lt;code&gt;&amp;lt;link rel=&amp;quot;apple-touch-icon&amp;quot;&amp;gt;&lt;/code&gt; tag at the bottom of the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of &lt;code&gt;index.html&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;    …&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/index.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;shortcut icon&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://cdn.glitch.com/49d34dc6-8fbd-46bb-8221-b99ffd36f1af%2Ftouchicon-180.png?v=1566411949736&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;del class=&quot;highlight-line highlight-line-remove&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- &amp;lt;link rel=&quot;apple-touch-icon&quot; href=&quot;https://cdn.glitch.com/49d34dc6-8fbd-46bb-8221-b99ffd36f1af%2Ftouchicon-180.png?v=1566411949736&quot;&gt; --&gt;&lt;/span&gt;&lt;/del&gt;&lt;br /&gt;&lt;ins class=&quot;highlight-line highlight-line-add&quot;&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;apple-touch-icon&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://cdn.glitch.com/49d34dc6-8fbd-46bb-8221-b99ffd36f1af%2Ftouchicon-180.png?v=1566411949736&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/ins&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;add-the-example-app-to-an-ios-home-screen-again&quot;&gt;Add the example app to an iOS home screen (again) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-apple-touch-icon/#add-the-example-app-to-an-ios-home-screen-again&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Try adding the example app to an iOS home screen again. This time, a proper icon is generated for
the site. If you audit the page again with Lighthouse you&#39;ll also see that the
&lt;strong&gt;Does not provide a valid &lt;code&gt;apple-touch-icon&lt;/code&gt;&lt;/strong&gt; audit now passes.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Apple touch icon.&quot; decoding=&quot;async&quot; height=&quot;1136&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 640px) 640px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Z8nauimUFUDPY8HJDGER.png?auto=format&amp;w=1280 1280w&quot; width=&quot;640&quot; /&gt;
  &lt;figcaption&gt;
    The Apple touch icon.
  &lt;/figcaption&gt;
&lt;/figure&gt;
</content>
    <author>
      <name>Kayce Basques</name>
    </author>
  </entry>
  
  <entry>
    <title>Discover performance opportunities with Lighthouse</title>
    <link href="https://web.dev/discover-performance-opportunities-with-lighthouse/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/discover-performance-opportunities-with-lighthouse/</id>
    <content type="html" mode="escaped">&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt; is a tool that
helps you measure and find ways to improve a page&#39;s performance. Here&#39;s the
general workflow for how you use Lighthouse:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You tell Lighthouse what page to audit.&lt;/li&gt;
&lt;li&gt;Lighthouse loads that page and records how long the page takes to hit
various performance milestones. These milestones are called &lt;strong&gt;metrics&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Lighthouse gives you a report on how the page did. The report provides a
score for each metric and a list of &lt;strong&gt;opportunities&lt;/strong&gt; which, if you implement
them, should make the page load faster.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Your mission is to improve your metrics scores over time, or at least make sure
that they don&#39;t get worse. There&#39;s no way to work on metrics directly, though.
Instead, you follow the opportunities that Lighthouse provides. Working on those
opportunities tends to improve your metrics scores.&lt;/p&gt;
&lt;h2 id=&quot;run-lighthouse-from-your-profile-page&quot;&gt;Run Lighthouse from your profile page &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/discover-performance-opportunities-with-lighthouse/#run-lighthouse-from-your-profile-page&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Run Lighthouse from your &lt;a href=&quot;https://web.dev/measure&quot;&gt;web.dev profile&lt;/a&gt; page:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Provide any URL, and Lighthouse runs a series of audits generating a report of how well the page did.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Review the audits report to identify areas in which your page can be improved.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For each audit, you&#39;ll find guidance and immediate steps you can take to improve your scores.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;run-lighthouse-from-chrome-devtools&quot;&gt;Run Lighthouse from Chrome DevTools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/discover-performance-opportunities-with-lighthouse/#run-lighthouse-from-chrome-devtools&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Chrome DevTools is the set of web developer tools that are built directly into
the Google Chrome browser. You don&#39;t have to download anything to get DevTools.
If you have Chrome, then you have DevTools.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In Chrome, go to the page that you want to audit.&lt;/li&gt;
&lt;li&gt;Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.&lt;/li&gt;
&lt;/ol&gt;
&lt;img alt=&quot;DevTools opened and docked to the right hand side of the screen.&quot; decoding=&quot;async&quot; height=&quot;475&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/yfEKnxDJWT3JY6FWNiOZ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Click the &lt;strong&gt;Audits&lt;/strong&gt; tab. If you don&#39;t see this tab, click the » symbol
and then select &lt;strong&gt;Audits&lt;/strong&gt; from the list. Lighthouse is the
engine that powers the &lt;strong&gt;Audits&lt;/strong&gt; panel. That&#39;s why you see an image of a
lighthouse.&lt;/p&gt;
&lt;img alt=&quot;DevTools opened to the Lighthouse audits panel.&quot; decoding=&quot;async&quot; height=&quot;475&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/ugJI2r5k9y3puAuCX66f.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;ol&gt;
&lt;li&gt;Make sure the &lt;strong&gt;Mobile&lt;/strong&gt; radio button is selected. When Lighthouse
audits your page, it will simulate a mobile device&#39;s viewport and user
agent string.&lt;/li&gt;
&lt;li&gt;Make sure the &lt;strong&gt;Performance&lt;/strong&gt; checkbox is enabled. You can enable or
disable the rest of the checkboxes in the &lt;strong&gt;Audits&lt;/strong&gt; section. If you enable
them, then you&#39;ll see a bunch of opportunities on ways to improve those
other aspects of your page.&lt;/li&gt;
&lt;li&gt;Make sure the &lt;strong&gt;Simulated Fast 3G, 4x CPU Slowdown&lt;/strong&gt; radio button is
selected. Lighthouse doesn&#39;t actually throttle your network or CPU while it
loads the page. Instead, it looks at how long the page took to load under
normal conditions, and then it estimates how long it would have taken on a
fast 3G network with a CPU that is 4 times less powerful than your machine&#39;s.&lt;/li&gt;
&lt;li&gt;Make sure that the &lt;strong&gt;Clear Storage&lt;/strong&gt; checkbox is enabled. This option
forces Lighthouse to go to the network for every page resource, which is
how first-time visitors experience the page.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Run Audits&lt;/strong&gt;. After 5 to 10 seconds, Lighthouse shows you a report.&lt;/li&gt;
&lt;/ol&gt;
&lt;img alt=&quot;DevTools showing a Lighthouse audit results report.&quot; decoding=&quot;async&quot; height=&quot;475&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/wy0Zq7fFXXSBeGwK8OgP.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; You can set the configuration options to whatever makes the most sense for your needs. If you don&#39;t understand them, the ones mentioned here are good defaults. If you can get your page fast with these options, then your page will be fast for everyone. The important thing is to stay consistent with the options across audits. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;For example, if you run some audits with &lt;strong&gt;Simulated Fast 3G&lt;/strong&gt;, &lt;strong&gt;4x CPU Slowdown
throttling enabled&lt;/strong&gt; and then other times you run audits with throttling
disabled, your metrics scores will be significantly lower when you have
throttling enabled. You might spend a lot of time trying to figure out why your
page is so much slower now, when in reality the only thing that changed was your
configuration.&lt;/p&gt;
&lt;h3 id=&quot;understand-your-report&quot;&gt;Understand your report &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/discover-performance-opportunities-with-lighthouse/#understand-your-report&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The top-right of your report lists your overall performance score. 100 is a
perfect score. Below the overall score are the metrics scores.
&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/performance-scoring/&quot; rel=&quot;noopener&quot;&gt;Lighthouse v3 Scoring Guide&lt;/a&gt;
explains how each metric score contributes to the overall score.&lt;/p&gt;
&lt;img alt=&quot;Lighthouse metrics scores showing green, passing scores, and yellow, warning scores.&quot; decoding=&quot;async&quot; height=&quot;504&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/3kKMRoc1EY2kHDy8NIHi.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Hover over a metric to learn more about it. Click &lt;strong&gt;Learn more&lt;/strong&gt; to read
documentation about it.&lt;/p&gt;
&lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;504&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jnSx7PT4NXRW6c88vVjj.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Below your metrics scores you see screenshots of how the page looked while it
loaded.&lt;/p&gt;
&lt;img alt=&quot;DevTools&amp;#x27; filmstrip view of a page loading.&quot; decoding=&quot;async&quot; height=&quot;504&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/hDyQRm4B1K1dRciNTvC1.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Below the screenshots you see opportunities for improving the page&#39;s
performance.&lt;/p&gt;
&lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;504&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xn9EDUNnxVhdD5FcfAMA.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Click an opportunity to learn more about it.&lt;/p&gt;
&lt;img alt=&quot;An expanded audit titled Defer offscreen images shows a number of image paths that can be optimized.&quot; decoding=&quot;async&quot; height=&quot;652&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/5dHwPPWIho3SckhvsGHt.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/discover-performance-opportunities-with-lighthouse/#next-steps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Try using Lighthouse to audit your page, either from your profile page or from
Chrome DevTools. Implement one of the opportunities, and then audit your page
again to see how the change affected your report. Your metrics scores should
ideally be a little better, and Lighthouse should no longer be flagging that
opportunity as something to work on.&lt;/p&gt;
&lt;p&gt;Running Lighthouse yourself is great for spot-checking issues, but ultimately
you&#39;ll want to setup continuous monitoring to make sure your site stays healthy.
To track your Lighthouse progress over time add your site to the your
&lt;a href=&quot;https://web.dev/measure&quot;&gt;profile&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Kayce Basques</name>
    </author>
  </entry>
  
  <entry>
    <title>Why HTTPS matters</title>
    <link href="https://web.dev/why-https-matters/"/>
    <updated>2015-11-23T00:00:00Z</updated>
    <id>https://web.dev/why-https-matters/</id>
    <content type="html" mode="escaped">&lt;p&gt;You should always protect all of your websites with HTTPS, even if they don&#39;t
handle sensitive communications. Aside from providing critical security and data
integrity for both your websites and your users&#39; personal information, HTTPS is
a requirement for many new browser features, particularly those required for
&lt;a href=&quot;https://web.dev/progressive-web-apps&quot;&gt;progressive web apps&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;iP75a1Y9saY&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/why-https-matters/#summary&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Intruders both malignant and benign exploit every unprotected resource between
your websites and users.&lt;/li&gt;
&lt;li&gt;Many intruders look at aggregate behaviors to identify your users.&lt;/li&gt;
&lt;li&gt;HTTPS doesn&#39;t just block misuse of your website. It&#39;s also a requirement for
many cutting-edge features and an enabling technology for app-like
capabilities such as service workers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;integrity&quot;&gt;HTTPS protects the integrity of your website &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/why-https-matters/#integrity&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;HTTPS helps prevent intruders from tampering with the communications
between your websites and your users&#39; browsers. Intruders include
intentionally malicious attackers, and legitimate but intrusive companies,
such as ISPs or hotels that inject ads into pages.&lt;/p&gt;
&lt;p&gt;Intruders exploit unprotected communications to trick your users into giving
up sensitive information or installing malware, or to insert their own
advertisements into your resources. For example, some third parties inject
advertisements into websites that potentially break user experiences and
create security vulnerabilities.&lt;/p&gt;
&lt;p&gt;Intruders exploit every unprotected resource that travels between your
websites and your users. Images, cookies, scripts, HTML… they&#39;re all
exploitable. Intrusions can occur at any point in the network, including a
user&#39;s machine, a Wi-Fi hotspot, or a compromised ISP, just to name a few.&lt;/p&gt;
&lt;h2 id=&quot;privacy&quot;&gt;HTTPS protects the privacy and security of your users &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/why-https-matters/#privacy&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;HTTPS prevents intruders from being able to passively listen to communications
between your websites and your users.&lt;/p&gt;
&lt;p&gt;One common misconception about HTTPS is that the only websites that need HTTPS
are those that handle sensitive communications. Every unprotected HTTP request
can potentially reveal information about the behaviors and identities of your
users. Although a single visit to one of your unprotected websites may seem
benign, some intruders look at the aggregate browsing activities of your users
to make inferences about their behaviors and intentions, and to &lt;a href=&quot;https://en.wikipedia.org/wiki/De-anonymization&quot; rel=&quot;noopener&quot;&gt;de-anonymize&lt;/a&gt;
their identities. For example, employees might inadvertently disclose sensitive
health conditions to their employers just by reading unprotected medical
articles.&lt;/p&gt;
&lt;h2 id=&quot;capabilities&quot;&gt;HTTPS is the future of the web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/why-https-matters/#capabilities&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Powerful, new web platform features, such as taking pictures or recording audio
with &lt;code&gt;getUserMedia()&lt;/code&gt;, enabling offline app experiences with &lt;a href=&quot;https://web.dev/service-workers-cache-storage/&quot;&gt;service workers&lt;/a&gt;, or
building &lt;a href=&quot;https://web.dev/progressive-web-apps&quot;&gt;progressive web apps&lt;/a&gt;, require explicit permission from the user before
executing. Many older APIs are also being updated to require permission to
execute, such as  the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Geolocation/Using_geolocation&quot; rel=&quot;noopener&quot;&gt;Geolocation API&lt;/a&gt;. HTTPS is a key component to the
permission workflows for both these new features and updated APIs.&lt;/p&gt;
</content>
    <author>
      <name>Kayce Basques</name>
    </author>
  </entry>
  
  <entry>
    <title>Read files in JavaScript</title>
    <link href="https://web.dev/read-files/"/>
    <updated>2010-06-18T00:00:00Z</updated>
    <id>https://web.dev/read-files/</id>
    <content type="html" mode="escaped">&lt;p&gt;Selecting and interacting with files on the user&#39;s local device is
one of the most commonly used features of the web. It allows users to select
files and upload them to a server, for example, uploading photos, or
submitting tax documents, etc. But, it also allows sites to read and
manipulate them without ever having to transfer the data across the network.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-file-system-access-api&quot;&gt;The modern File System Access API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#the-modern-file-system-access-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The File System Access API provides an easy way to both read from
and write to files and directories on the user&#39;s local system. It&#39;s currently
available in most Chromium-based browsers such as Chrome or Edge. To learn
more about it, see the &lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt; article.&lt;/p&gt;
&lt;p&gt;Since the File System Access API is not compatible with all browsers yet,
check out &lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access&quot; rel=&quot;noopener&quot;&gt;browser-fs-access&lt;/a&gt;,
a helper library that uses the new API wherever it is available, but falls
back to legacy approaches when it is not.&lt;/p&gt;
&lt;h2 id=&quot;working-with-files,-the-classic-way&quot;&gt;Working with files, the classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#working-with-files,-the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This guide shows you how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select files
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/read-files/#select-input&quot;&gt;Using the HTML input element&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/read-files/#select-dnd&quot;&gt;Using a drag-and-drop zone&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/read-files/#read-metadata&quot;&gt;Read file metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/read-files/#read-content&quot;&gt;Read a file&#39;s content&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;select&quot;&gt;Select files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#select&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;select-input&quot;&gt;HTML input element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#select-input&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The easiest way for users to select files is using the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/input/file&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt;&lt;/a&gt; element, which is supported in every
major browser. When clicked, it lets a user select a file, or multiple files
if the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/input/file#Additional_attributes&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;multiple&lt;/code&gt;&lt;/a&gt; attribute is included, using
their operating system&#39;s built-in file selection UI. When the user finishes
selecting a file or files, the element&#39;s &lt;code&gt;change&lt;/code&gt; event fires. You can
access the list of files from &lt;code&gt;event.target.files&lt;/code&gt;, which is a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileList&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileList&lt;/code&gt;&lt;/a&gt; object. Each item in the &lt;code&gt;FileList&lt;/code&gt; is a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/File&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; object.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- The `multiple` attribute lets users select multiple files. --&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;file-selector&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;multiple&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileSelector &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;file-selector&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  fileSelector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;change&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileList &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileList&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Check if the &lt;a href=&quot;https://web.dev/file-system-access/#ask-the-user-to-pick-a-file-to-read&quot;&gt;&lt;code&gt;window.showOpenFilePicker()&lt;/code&gt;&lt;/a&gt; method is a viable alternative for your use case, since it also gives you a file handle so you can possibly write back to the file, in addition to reading. This method can be &lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access#opening-files&quot;&gt;polyfilled&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;This example lets a user select multiple files using their operating system&#39;s
built-in file selection UI and then logs each selected file to the console.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 480px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/input-type-file?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;input-type-file on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h4 id=&quot;accept&quot;&gt;Limit the types of files users can select &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#accept&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;In some cases, you may want to limit the types of files users can select.
For example, an image editing app should only accept images, not text files.
To do that, add an &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/input/file#Additional_attributes&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;accept&lt;/code&gt;&lt;/a&gt; attribute to
the input element to specify which file types are accepted.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;file-selector&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;.jpg, .jpeg, .png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;select-dnd&quot;&gt;Custom drag-and-drop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#select-dnd&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In some browsers, the &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element is also a drop target,
allowing users to drag-and-drop files into your app. But, the drop target is
small, and can be hard to use. Instead, once you&#39;ve provided the core
functionality using an &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element, you can provide a
large, custom drag-and-drop surface.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Check if the &lt;a href=&quot;https://web.dev/file-system-access/#drag-and-drop-integration&quot;&gt;&lt;code&gt;DataTransferItem.getAsFileSystemHandle()&lt;/code&gt;&lt;/a&gt; method is a viable alternative for your use case, since it also gives you a file handle so you can possibly write back to the file, in addition to reading. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;choose-drop-zone&quot;&gt;Choose your drop zone &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#choose-drop-zone&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Your drop surface depends on the design of your application. You may
only want part of the window to be a drop surface, or potentially the entire
window.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A screenshot of Squoosh, an image compression web app.&quot; decoding=&quot;async&quot; height=&quot;589&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Squoosh makes the entire window a drop zone.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Squoosh allows the user to drag and drop an image anywhere into the window,
and clicking &lt;strong&gt;select an image&lt;/strong&gt; invokes the &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element.
Whatever you choose as your drop zone, make sure it&#39;s clear to the user that
they can drag and drop files onto that surface.&lt;/p&gt;
&lt;h4 id=&quot;define-drop-zone&quot;&gt;Define the drop zone &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#define-drop-zone&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To enable an element to be a drag-and-drop zone, you&#39;ll need to listen for
two events, &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Document/dragover_event&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;dragover&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Document/drop_event&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;drop&lt;/code&gt;&lt;/a&gt;. The &lt;code&gt;dragover&lt;/code&gt;
event updates the browser UI to visually indicate that the drag-and-drop
action is creating a copy of the file. The &lt;code&gt;drop&lt;/code&gt; event is fired after the
user drops the files onto the surface. Similar to the input element,
you can access the list of files from &lt;code&gt;event.dataTransfer.files&lt;/code&gt;, which is
a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileList&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileList&lt;/code&gt;&lt;/a&gt; object. Each item in the &lt;code&gt;FileList&lt;/code&gt; is a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/File&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; object.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dropArea &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;drop-area&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;dropArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;dragover&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stopPropagation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Style the drag-and-drop as a &quot;copy file&quot; operation.&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dropEffect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;copy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;dropArea&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;drop&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stopPropagation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileList &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileList&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Event/stopPropagation&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;event.stopPropagation()&lt;/code&gt;&lt;/a&gt; and
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Event/preventDefault&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;event.preventDefault()&lt;/code&gt;&lt;/a&gt; stop the browser&#39;s default
behavior and allow your code to run instead. Without them,
the browser would otherwise navigate away from your page and open the files
the user dropped into the browser window.&lt;/p&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://custom-drag-and-drop.glitch.me/&quot; rel=&quot;noopener&quot;&gt;Custom drag-and-drop&lt;/a&gt; for a live demonstration.&lt;/p&gt;
&lt;h3 id=&quot;directories&quot;&gt;What about directories? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#directories&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unfortunately, today there isn&#39;t a good way to access a directory.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLInputElement/webkitdirectory&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;webkitdirectory&lt;/code&gt;&lt;/a&gt; attribute on the &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element allows the user to choose a directory or directories. It&#39;s
&lt;a href=&quot;https://caniuse.com/#search=webkitdirectory&quot; rel=&quot;noopener&quot;&gt;supported in most major browsers&lt;/a&gt; except for Firefox
for Android.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Check if the &lt;a href=&quot;https://web.dev/file-system-access/#opening-a-directory-and-enumerating-its-contents&quot;&gt;&lt;code&gt;window.showDirectoryPicker()&lt;/code&gt;&lt;/a&gt; method is a viable alternative for your use case, since it also gives you a directory handle so you can possibly write back to the directory, in addition to reading. This method can be &lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access#opening-directories&quot;&gt;polyfilled&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;If drag-and-drop is enabled, a user may try to drag a directory into the
drop zone. When the drop event is fired, it will include a &lt;code&gt;File&lt;/code&gt; object for
the directory, but does not provide access any of the files within the
directory.&lt;/p&gt;
&lt;h2 id=&quot;read-metadata&quot;&gt;Read file metadata &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#read-metadata&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/File&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; object contains metadata about
the file. Most browsers provide the file name, the size of the file, and the
MIME type, though depending on the platform, different browsers may provide
different, or additional information.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getMetadataForFileList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;fileList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; fileList&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Not supported in Safari for iOS.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;NOT SUPPORTED&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Not supported in Firefox for Android or Opera for Android.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;NOT SUPPORTED&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Unknown cross-browser support.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;NOT SUPPORTED&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; size&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can see this in action in the &lt;a href=&quot;https://input-type-file.glitch.me/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;input-type-file&lt;/code&gt;&lt;/a&gt; demo.&lt;/p&gt;
&lt;h2 id=&quot;read-content&quot;&gt;Read a file&#39;s content &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#read-content&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To read a file, use &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileReader&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileReader&lt;/code&gt;&lt;/a&gt;, which enables you to read
the content of a &lt;code&gt;File&lt;/code&gt; object into memory. You can instruct &lt;code&gt;FileReader&lt;/code&gt;
to read a file as an &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileReader/readAsArrayBuffer&quot; rel=&quot;noopener&quot;&gt;array buffer&lt;/a&gt;, a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileReader/readAsDataURL&quot; rel=&quot;noopener&quot;&gt;data URL&lt;/a&gt;, or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileReader/readAsText&quot; rel=&quot;noopener&quot;&gt;text&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Check if the file is an image.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;image/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;File is not an image.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FileReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readAsDataURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The example above reads a &lt;code&gt;File&lt;/code&gt; provided by the user, then converts it to a
data URL, and uses that data URL to display the image in an &lt;code&gt;img&lt;/code&gt; element.
Check out the &lt;a href=&quot;https://read-image-file.glitch.me/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;read-image-file&lt;/code&gt;&lt;/a&gt; demo to see how to
verify that the user has selected an image file.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 480px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/read-image-file?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;read-image-file on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3 id=&quot;monitor-progress&quot;&gt;Monitor the progress of a file read &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#monitor-progress&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When reading large files, it may be helpful to provide some UX to indicate
how far the read has progressed. For that, use the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileReader/progress_event&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;progress&lt;/code&gt;&lt;/a&gt; event provided by &lt;code&gt;FileReader&lt;/code&gt;. The
&lt;code&gt;progress&lt;/code&gt; event provides two properties, &lt;code&gt;loaded&lt;/code&gt; (the amount read) and
&lt;code&gt;total&lt;/code&gt; (the amount to read).&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FileReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// Do something with result&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;progress&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loaded &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; percent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loaded &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Progress: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;percent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readAsDataURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Hero image by Vincent Botta from &lt;a href=&quot;https://unsplash.com/photos/bv_rJXpNU9I&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content>
    <author>
      <name>Kayce Basques</name>
    </author><author>
      <name>Pete LePage</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
</feed>
