<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://web.dev/</id>
  <title>Jeff Posnick on web.dev</title>
  <updated>2026-04-15T23:21:06Z</updated>
  <author>
    <name>Jeff Posnick</name>
  </author>
  <link href="https://web.dev/authors/jeffposnick/feed.xml" rel="self"/>
  <link href="https://web.dev/"/>
  <icon>https://web-dev.imgix.net/image/FNkVSAX8UDTTQWQkKftSgGe9clO2/aNrmVViMdqMvdXZn0NHA.jpg?auto=format</icon>
  <logo>https://web.dev/images/shared/rss-banner.png</logo>
  <subtitle>Web DevRel @ Google</subtitle>
  
  
  <entry>
    <title>ES modules in service workers</title>
    <link href="https://web.dev/es-modules-in-sw/"/>
    <updated>2021-05-13T00:00:00Z</updated>
    <id>https://web.dev/es-modules-in-sw/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;background&quot;&gt;Background &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/es-modules-in-sw/#background&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Guide/Modules&quot; rel=&quot;noopener&quot;&gt;ES modules&lt;/a&gt;
have been a developer favorite for a while now. In addition to a
&lt;a href=&quot;https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/&quot; rel=&quot;noopener&quot;&gt;number of other benefits&lt;/a&gt;,
they offer the promise of a universal module format where shared code can be
released once and run in browsers and in alternative runtimes like
&lt;a href=&quot;https://nodejs.org/en/&quot; rel=&quot;noopener&quot;&gt;Node.js&lt;/a&gt;. While
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Guide/Modules#import&quot; rel=&quot;noopener&quot;&gt;all modern browsers&lt;/a&gt;
offer some ES module support, they don&#39;t all offer support &lt;em&gt;everywhere&lt;/em&gt; that
code can be run. Specifically, support for importing ES modules inside of a
browser&#39;s
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Service_Worker_API/Using_Service_Workers&quot; rel=&quot;noopener&quot;&gt;service worker&lt;/a&gt;
is just starting to become more widely available.&lt;/p&gt;
&lt;p&gt;This article details the current state of ES module support in service workers
across common browsers, along with some gotchas to avoid, and best practices for
shipping backwards-compatible service worker code.&lt;/p&gt;
&lt;h2 id=&quot;use-cases&quot;&gt;Use cases &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/es-modules-in-sw/#use-cases&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The ideal use case for ES modules inside of service workers is for loading a
modern library or configuration code that&#39;s shared with other runtimes that
support ES modules.&lt;/p&gt;
&lt;p&gt;Attempting to share code in this way prior to ES modules entailed using older
&amp;quot;universal&amp;quot; module formats like &lt;a href=&quot;https://github.com/umdjs/umd&quot; rel=&quot;noopener&quot;&gt;UMD&lt;/a&gt; that include
unneeded boilerplate, and writing code that made changes to globally exposed
variables.&lt;/p&gt;
&lt;p&gt;Scripts imported via ES modules can trigger the service worker
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#updates&quot;&gt;update&lt;/a&gt;
flow if their contents change, matching the
&lt;a href=&quot;https://developer.chrome.com/blog/fresher-sw/#checks-for-updates-to-imported-scripts&quot; rel=&quot;noopener&quot;&gt;behavior&lt;/a&gt;
of
&lt;code&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WorkerGlobalScope/importScripts&quot; rel=&quot;noopener&quot;&gt;importScripts()&lt;/a&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;current-limitations&quot;&gt;Current limitations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/es-modules-in-sw/#current-limitations&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;static-imports-only&quot;&gt;Static imports only &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/es-modules-in-sw/#static-imports-only&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;ES modules can be imported in one of two ways: either
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import&quot; rel=&quot;noopener&quot;&gt;statically&lt;/a&gt;,
using the &lt;code&gt;import ... from &#39;...&#39;&lt;/code&gt; syntax, or
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports&quot; rel=&quot;noopener&quot;&gt;dynamically&lt;/a&gt;,
using the &lt;code&gt;import()&lt;/code&gt; method. Inside of a service worker, only the static
syntax is currently supported.&lt;/p&gt;
&lt;p&gt;This limitation is analogous to a
&lt;a href=&quot;https://developer.chrome.com/blog/tweeks-to-addAll-importScripts/&quot; rel=&quot;noopener&quot;&gt;similar restriction&lt;/a&gt;
placed on &lt;code&gt;importScripts()&lt;/code&gt; usage. Dynamic calls to &lt;code&gt;importScripts()&lt;/code&gt; do not
work inside of a service worker, and all &lt;code&gt;importScripts()&lt;/code&gt; calls, which are
inherently synchronous, must complete before the service worker completes its
&lt;code&gt;install&lt;/code&gt; phase. This restriction ensures that the browser knows about, and is
able to implicitly cache, all JavaScript code needed for a service worker&#39;s
implementation during installation.&lt;/p&gt;
&lt;p&gt;Eventually, this restriction might be lifted, and dynamic ES
module imports
&lt;a href=&quot;https://github.com/w3c/ServiceWorker/issues/1356#issuecomment-783220858&quot; rel=&quot;noopener&quot;&gt;may be allowed&lt;/a&gt;.
For now, ensure that you only use the static syntax inside of
a service worker.&lt;/p&gt;
&lt;h4 id=&quot;what-about-other-workers&quot;&gt;What about other workers? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/es-modules-in-sw/#what-about-other-workers&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Support for
&lt;a href=&quot;https://web.dev/module-workers/&quot;&gt;ES modules in &amp;quot;dedicated&amp;quot; workers&lt;/a&gt;—those
constructed with &lt;code&gt;new Worker(&#39;...&#39;, {type: &#39;module&#39;})&lt;/code&gt;—is more widespread, and
has been supported in Chrome and Edge since
&lt;a href=&quot;https://chromestatus.com/feature/5761300827209728&quot; rel=&quot;noopener&quot;&gt;version 80&lt;/a&gt;, as well as
&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=164860&quot; rel=&quot;noopener&quot;&gt;recent versions&lt;/a&gt; of Safari.
Both static and dynamic ES module imports are supported in dedicated workers.&lt;/p&gt;
&lt;p&gt;Chrome and Edge have supported ES modules in
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/SharedWorker&quot; rel=&quot;noopener&quot;&gt;shared workers&lt;/a&gt;
since &lt;a href=&quot;https://chromestatus.com/feature/5169440012369920&quot; rel=&quot;noopener&quot;&gt;version 83&lt;/a&gt;, but no
other browser offers support at this time.&lt;/p&gt;
&lt;h3 id=&quot;no-support-for-import-maps&quot;&gt;No support for import maps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/es-modules-in-sw/#no-support-for-import-maps&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/WICG/import-maps/blob/main/README.md&quot; rel=&quot;noopener&quot;&gt;Import maps&lt;/a&gt; allow
runtime environments to rewrite module specifiers, to, for example, prepend the
URL of a preferred CDN from which the ES modules can be loaded.&lt;/p&gt;
&lt;p&gt;While Chrome and Edge
&lt;a href=&quot;https://www.chromestatus.com/feature/5315286962012160&quot; rel=&quot;noopener&quot;&gt;version 89&lt;/a&gt; and above
support import maps, they currently
&lt;a href=&quot;https://github.com/WICG/import-maps/issues/2&quot; rel=&quot;noopener&quot;&gt;cannot be used&lt;/a&gt; with service
workers.&lt;/p&gt;
&lt;h2 id=&quot;browser-support&quot;&gt;Browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/es-modules-in-sw/#browser-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 61, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      61
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 60, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      60
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 16, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      16
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 10.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      10.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;ES modules in service workers are supported in Chrome and Edge starting with
&lt;a href=&quot;https://chromestatus.com/feature/4609574738853888&quot; rel=&quot;noopener&quot;&gt;version 91&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Safari added support in the
&lt;a href=&quot;https://webkit.org/blog/11577/release-notes-for-safari-technology-preview-122/#:~:text=Added%20support%20for%20modules%20in%20Service%20Workers&quot; rel=&quot;noopener&quot;&gt;Technology Preview 122 Release&lt;/a&gt;,
and developers should expect to see this functionality released in the stable
version of Safari in the future.&lt;/p&gt;
&lt;h2 id=&quot;example-code&quot;&gt;Example code &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/es-modules-in-sw/#example-code&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is a basic example of using a shared ES module in a web app&#39;s &lt;code&gt;window&lt;/code&gt;
context, while also registering a service worker that uses the same ES module:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Inside config.js:&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cacheName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;my-cache&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Inside your web app:&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;script type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cacheName&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./config.js&#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;// Do something with cacheName.&lt;/span&gt;&lt;br /&gt;&lt;br /&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;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;es-module-sw.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;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;module&#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;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Inside es-module-sw.js:&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;cacheName&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./config.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;self&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;install&#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;waitUntil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cacheName&lt;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;// ...&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;backwards-compatibility&quot;&gt;Backwards compatibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/es-modules-in-sw/#backwards-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The above example would work fine if all browsers supported ES modules in
service workers, but as of this writing, that&#39;s not the case.&lt;/p&gt;
&lt;p&gt;To accommodate browsers that don&#39;t have built-in support, you can run your
service worker script through an
&lt;a href=&quot;https://bundlers.tooling.report/&quot; rel=&quot;noopener&quot;&gt;ES module-compatible bundler&lt;/a&gt; to create a
service worker that includes all of the module code inline, and will work in
older browsers. Alternatively, if the modules you&#39;re attempting to import are
already available bundled in
&lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/IIFE&quot; rel=&quot;noopener&quot;&gt;IIFE&lt;/a&gt; or
&lt;a href=&quot;https://github.com/umdjs/umd&quot; rel=&quot;noopener&quot;&gt;UMD&lt;/a&gt; formats, you can import them using
&lt;code&gt;importScripts()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once you have two versions of your service worker available—one that uses ES
modules, and the other that doesn&#39;t—you&#39;ll need to detect what the current
browser supports, and register the corresponding service worker script. The best
practices for detecting support are currently in flux, but you can follow the
discussion in this
&lt;a href=&quot;https://github.com/w3c/ServiceWorker/issues/1582&quot; rel=&quot;noopener&quot;&gt;GitHub issue&lt;/a&gt; for
recommendations.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@vlado?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Vlado
Paunovic&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/@vlado?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Handling range requests in a service worker</title>
    <link href="https://web.dev/sw-range-requests/"/>
    <updated>2020-10-06T00:00:00Z</updated>
    <id>https://web.dev/sw-range-requests/</id>
    <content type="html" mode="escaped">&lt;p&gt;Some HTTP requests contain a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Range&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Range:&lt;/code&gt; header&lt;/a&gt;, indicating that only a portion of the full resource should be returned. They&#39;re commonly used for streaming audio or video content to allow smaller chunks of media to be loaded on demand, instead of requesting the entirety of the remote file all at once.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;https://developer.chrome.com/docs/workbox/service-worker-overview/&quot; rel=&quot;noopener&quot;&gt;service worker&lt;/a&gt; is JavaScript code that sits in between your web app and the network, potentially intercepting outgoing network requests and generating responses for them.&lt;/p&gt;
&lt;p&gt;Historically, range requests and service workers haven&#39;t played nicely together. It&#39;s been necessary to take special steps to avoid bad outcomes in your service worker. Fortunately, this is starting to change. In browsers exhibiting the correct behavior, range requests will &amp;quot;just work&amp;quot; when passing through a service worker.&lt;/p&gt;
&lt;h2 id=&quot;whats-the-issue&quot;&gt;What&#39;s the issue? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/sw-range-requests/#whats-the-issue&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Consider a service worker with the following &lt;code&gt;fetch&lt;/code&gt; event listener, which takes every incoming request and passes it to the network:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;self&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;fetch&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// The Range: header will not pass through in&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// browsers that behave incorrectly.&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;respondWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&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;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This sort of trivial &lt;code&gt;fetch&lt;/code&gt; event listener should &lt;a href=&quot;https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#never_use_a_passthrough_fetch_handler&quot;&gt;normally be avoided&lt;/a&gt;; it&#39;s used here for illustrative purposes. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;In browsers with the incorrect behavior, if &lt;code&gt;event.request&lt;/code&gt; included a &lt;code&gt;Range:&lt;/code&gt; header, that header would be silently dropped. The request that was received by the remote server would not include &lt;code&gt;Range:&lt;/code&gt; at all. This would not necessarily &amp;quot;break&amp;quot; anything, since a server is &lt;em&gt;technically&lt;/em&gt; allowed to return the full response body, with a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Status/200&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;200&lt;/code&gt; status code&lt;/a&gt;, even when a &lt;code&gt;Range:&lt;/code&gt; header is present in the original request. But it would result in more data being transferred than is strictly needed from the perspective of the browser.&lt;/p&gt;
&lt;p&gt;Developers who were aware of this behavior could work around it by explicitly checking for the presence of a &lt;code&gt;Range:&lt;/code&gt; header, and not calling &lt;code&gt;event.respondWith()&lt;/code&gt; if one is present. By doing this, the service worker effectively removes itself from the response generation picture, and the default browser networking logic, which knows how to preserve range requests, is used instead.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;self&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;fetch&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Return without calling event.respondWith()&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// if this is a range request.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;range&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token 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;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&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;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;It&#39;s safe to say that most developers were not aware of the need to do this, though. And it wasn&#39;t clear &lt;em&gt;why&lt;/em&gt; that should be required. Ultimately, this limitation was due to &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=847428&quot; rel=&quot;noopener&quot;&gt;browsers&lt;/a&gt; needing to catch up to &lt;a href=&quot;https://github.com/whatwg/fetch/pull/560&quot; rel=&quot;noopener&quot;&gt;changes in the underlying specification&lt;/a&gt;, which added support for this functionality.&lt;/p&gt;
&lt;h2 id=&quot;whats-been-fixed&quot;&gt;What&#39;s been fixed? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/sw-range-requests/#whats-been-fixed&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Browsers that behave correctly preserve the &lt;code&gt;Range:&lt;/code&gt; header when &lt;code&gt;event.request&lt;/code&gt; is passed to &lt;code&gt;fetch()&lt;/code&gt;. This means the service worker code in my initial example will allow the remote server to see the &lt;code&gt;Range:&lt;/code&gt; header, if it was set by the browser:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;self&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;fetch&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// The Range: header will pass through in browsers&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// that behave correctly.&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;respondWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&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;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The server now gets a chance to properly handle the range request and return a partial response with a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Status/206&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;206&lt;/code&gt; status code&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;which-browsers-behave-correctly&quot;&gt;Which browsers behave correctly? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/sw-range-requests/#which-browsers-behave-correctly&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Recent versions of Safari have the &lt;a href=&quot;https://trac.webkit.org/changeset/252047/webkit&quot; rel=&quot;noopener&quot;&gt;correct functionality&lt;/a&gt;. Chrome and Edge, starting with &lt;a href=&quot;https://chromestatus.com/feature/5648276147666944&quot; rel=&quot;noopener&quot;&gt;version 87&lt;/a&gt;, behave correctly as well.&lt;/p&gt;
&lt;p&gt;As of this October 2020, Firefox has not yet fixed this behavior, so you may still need to account for it while deploying your service worker&#39;s code to production.&lt;/p&gt;
&lt;p&gt;Checking the &amp;quot;Include range header in network request&amp;quot; row of the &lt;a href=&quot;https://wpt.fyi/results/fetch/range/sw.https.window.html?label=master&amp;amp;label=experimental&amp;amp;aligned&quot; rel=&quot;noopener&quot;&gt;Web Platform Tests dashboard&lt;/a&gt; is the best way to confirm whether or not a given browser has corrected this behavior.&lt;/p&gt;
&lt;h2 id=&quot;what-about-serving-range-requests-from-the-cache&quot;&gt;What about serving range requests from the cache? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/sw-range-requests/#what-about-serving-range-requests-from-the-cache&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Service workers can do much more than just pass a request through to the network. A common use case is to add resources, like audio and video files, to a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/CacheStorage&quot; rel=&quot;noopener&quot;&gt;local cache&lt;/a&gt;. A service worker can then fulfill requests from that cache, bypassing the network entirely.&lt;/p&gt;
&lt;p&gt;All browsers, including Firefox, support inspecting a request inside a &lt;code&gt;fetch&lt;/code&gt; handler, checking for the presence of the &lt;code&gt;Range:&lt;/code&gt; header, and then locally fulfilling the request with a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Status/206&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;206&lt;/code&gt; response&lt;/a&gt; that comes from a cache. The service worker code to properly parse the &lt;code&gt;Range:&lt;/code&gt; header and return only the appropriate segment of the complete cached response is not trivial, though.&lt;/p&gt;
&lt;p&gt;Fortunately, developers who want some help can turn to &lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt;, which is a set of libraries that simplifies common service worker use cases. The &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-range-request/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;workbox-range-request module&lt;/code&gt;&lt;/a&gt; implements all the logic necessary to serve partial responses directly from the cache. A full recipe for this use case can be found &lt;a href=&quot;https://developer.chrome.com/docs/workbox/serving-cached-audio-and-video/&quot; rel=&quot;noopener&quot;&gt;in the Workbox documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The hero image on this post is by &lt;a href=&quot;https://unsplash.com/photos/OnAwTs0tu3k&quot; rel=&quot;noopener&quot;&gt;Natalie Rhea Riggs&lt;/a&gt; on Unsplash.&lt;/em&gt;&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Handling navigation requests</title>
    <link href="https://web.dev/handling-navigation-requests/"/>
    <updated>2020-07-13T00:00:00Z</updated>
    <id>https://web.dev/handling-navigation-requests/</id>
    <content type="html" mode="escaped">&lt;p&gt;Navigation requests are requests for HTML documents made by your browser whenever you enter a new
URL in the navigation bar, or follow a link on a page taking you to a new URL. This is where service
workers make their biggest impact on performance: if you use a service worker to respond to
navigation requests without waiting for the network, you can ensure that navigations are reliably
fast, in addition to being resilient when the network is unavailable. This is the single biggest
performance win that comes from a service worker, versus what&#39;s possible with &lt;a href=&quot;https://web.dev/http-cache/&quot;&gt;HTTP
caching&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As detailed in the &lt;a href=&quot;https://web.dev/identify-resources-via-network-panel/&quot;&gt;Identify resources loaded from the
network&lt;/a&gt; guide, a navigation request is the first of
potentially many requests made in the
&lt;a href=&quot;https://developer.chrome.com/docs/devtools/network/reference/#waterfall&quot; rel=&quot;noopener&quot;&gt;&amp;quot;waterfall&amp;quot;&lt;/a&gt;
of network traffic. The HTML that you load via a navigation request kicks off the flow of all other
requests for subresources like images, scripts, and styles.&lt;/p&gt;
&lt;p&gt;Inside of a service worker&#39;s &lt;code&gt;fetch&lt;/code&gt; event handler, you can determine whether a request is a
navigation by checking the &lt;code&gt;request.mode&lt;/code&gt; property on the &lt;code&gt;FetchEvent&lt;/code&gt;. If it&#39;s set to &lt;code&gt;&#39;navigate&#39;&lt;/code&gt;,
then it&#39;s a navigation request.&lt;/p&gt;
&lt;p&gt;As a general rule, do not use long-lived &lt;code&gt;&lt;a href=&quot;https://web.dev/http-cache/&quot;&gt;Cache-Control headers&lt;/a&gt;&lt;/code&gt; to cache
the HTML response for a navigation request. They should normally be satisfied via the network, with
&lt;code&gt;Cache-Control: no-cache&lt;/code&gt;, to ensure that the HTML, along with the chain of subsequent
network requests, is (reasonably) fresh. Going against the network each time the user navigates to a
new page unfortunately means that each navigation &lt;em&gt;might&lt;/em&gt; be slow. At the very least, it
means that it won&#39;t be &lt;em&gt;reliably&lt;/em&gt; fast.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; &lt;code&gt;Cache-Control: no-cache&lt;/code&gt; means the browser must check (or &amp;quot;revalidate&amp;quot;) with the server before using a previously cached resource. This requires a round-trip network communication to complete before the resource can be used. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;different-approaches-for-architectures&quot;&gt;Different approaches for architectures &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handling-navigation-requests/#different-approaches-for-architectures&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Figuring out &lt;em&gt;how&lt;/em&gt; to respond to navigation requests while avoiding the network can be tricky. The
right approach depends very much on your web site&#39;s architecture and the number of unique URLs that
users might navigate to.&lt;/p&gt;
&lt;p&gt;While there&#39;s no one-size-fits all solution, the following general guidelines should help you decide
which approach is the most viable.&lt;/p&gt;
&lt;h3 id=&quot;small-static-sites&quot;&gt;Small static sites &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handling-navigation-requests/#small-static-sites&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If your web app consists of a relatively small number (think: a couple of dozen) unique URLs, and
each of those URLs corresponds to a different static HTML file, then one viable approach is to just
cache all of those HTML files, and respond to navigation requests with the appropriate cached HTML.&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&quot;https://web.dev/precache-with-workbox/&quot;&gt;precaching&lt;/a&gt;, you can cache the HTML in advance, as soon as the
service worker is installed, and update the cached HTML each time you rebuild your site and redeploy
your service worker.&lt;/p&gt;
&lt;p&gt;Alternatively, if you would rather avoid precaching all of your HTML—perhaps because users tend to
navigate to only a subset of URLs on your site—you can use a
&lt;a href=&quot;https://web.dev/runtime-caching-with-workbox/#stale-while-revalidate&quot;&gt;stale-while-revalidate&lt;/a&gt; runtime caching
strategy. Be careful about this approach, though, as each individual HTML document is cached and
updated separately. Using runtime caching for HTML is most appropriate if you have a small number of
URLs that are revisited &lt;strong&gt;frequently&lt;/strong&gt; by the same set of users, and if you feel comfortable about
those URLs being revalidated independently of each other.&lt;/p&gt;
&lt;h3 id=&quot;single-page-apps&quot;&gt;Single-page apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handling-navigation-requests/#single-page-apps&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A single-page architecture is frequently used by modern web applications. In it, client-side
JavaScript modifies the HTML in response to user actions. This model uses the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/History_API&quot; rel=&quot;noopener&quot;&gt;History
API&lt;/a&gt; to modify the current URL as the
user interacts with the web app, leading to what&#39;s effectively a &amp;quot;simulated&amp;quot; navigation. While
subsequent navigations might be &amp;quot;fake&amp;quot;, the initial navigation is real, and it&#39;s still important to
make sure that it isn&#39;t blocked on the network.&lt;/p&gt;
&lt;p&gt;Fortunately, if you&#39;re using the single-page architecture, there&#39;s a straightforward pattern to
follow for serving the initial navigation from the cache: the &lt;a href=&quot;https://web.dev/learn/pwa/architecture/&quot;&gt;application
shell&lt;/a&gt;. In this model, your
service worker responds to navigation requests by returning the same, single HTML file that has
already been precached—regardless of the URL being requested. This HTML should be bare-bones,
consisting of, perhaps, a generic loading indicator or &lt;a href=&quot;https://css-tricks.com/building-skeleton-screens-css-custom-properties/&quot; rel=&quot;noopener&quot;&gt;skeleton
content&lt;/a&gt;. Once the browser
has loaded this HTML from the cache, your existing client-side JavaScript takes over, and renders
the correct HTML content for the URL from the original navigation request.&lt;/p&gt;
&lt;p&gt;Workbox provides the tools that you need to implement this approach; the &lt;code&gt;&lt;a href=&quot;https://developer.chrome.com/docs/workbox/reference/workbox-build/#method-generateSW&quot; rel=&quot;noopener&quot;&gt;navigateFallback
option&lt;/a&gt;&lt;/code&gt;
allows you to specify which HTML document to use as your app shell, along with an optional allow and
deny list to limit this behavior to a subset of your URLs.&lt;/p&gt;
&lt;h3 id=&quot;multi-page-apps&quot;&gt;Multi-page apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handling-navigation-requests/#multi-page-apps&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If your web server generates your site&#39;s HTML dynamically, or if you have more than a few dozen
unique pages,  then it&#39;s much harder to avoid the network when handling navigation requests. The
advice in &lt;a href=&quot;https://web.dev/handling-navigation-requests/#everything-else&quot;&gt;Everything else&lt;/a&gt; will likely apply to you.&lt;/p&gt;
&lt;p&gt;But for a certain subset of multi-page apps, you might be able to implement a service worker that
fully replicates the logic used in your web server to generate HTML. This works best if you can
share routing and templating information between the server and service worker environments, and in
particular, if your web server uses JavaScript (without relying on
&lt;a href=&quot;https://nodejs.org/&quot; rel=&quot;noopener&quot;&gt;Node.js&lt;/a&gt;-specific features, like file system access).&lt;/p&gt;
&lt;p&gt;If your web server falls into that category and you would like to explore one approach to moving
HTML generation off the network and into your service worker, the guidance in &lt;a href=&quot;https://developer.chrome.com/blog/beyond-spa/&quot; rel=&quot;noopener&quot;&gt;Beyond SPAs:
alternative architectures for your
PWA&lt;/a&gt; can get you started.&lt;/p&gt;
&lt;h3 id=&quot;everything-else&quot;&gt;Everything else &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handling-navigation-requests/#everything-else&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you can&#39;t respond to navigation requests with cached HTML, you must take steps to ensure that
adding a service worker to your site (to handle other, non-HTML requests) doesn&#39;t end up slowing
down your navigations. Starting up the service worker without using it to respond to a navigation
request will introduce a small amount of latency (as explained in &lt;a href=&quot;https://youtu.be/25aCD5XL1Jk&quot; rel=&quot;noopener&quot;&gt;Building Faster, More Resilient
Apps with Service Worker&lt;/a&gt;). You can mitigate this overhead by enabling
a feature called &lt;a href=&quot;https://developer.chrome.com/blog/navigation-preload/&quot; rel=&quot;noopener&quot;&gt;navigation
preload&lt;/a&gt;, and then &lt;a href=&quot;https://developer.chrome.com/blog/navigation-preload/#using-the-preloaded-response&quot; rel=&quot;noopener&quot;&gt;using the
network
response&lt;/a&gt;
that&#39;s been preloaded inside of your &lt;code&gt;fetch&lt;/code&gt; event handler.&lt;/p&gt;
&lt;p&gt;Workbox &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-navigation-preload/&quot; rel=&quot;noopener&quot;&gt;provides a helper
library&lt;/a&gt; that
feature-detects whether navigation preload is supported, and if so, simplifies the process of
telling your service worker to use the network response.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;span&gt;Photo by &lt;a href=&quot;https://unsplash.com/@aaronburden?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Aaron Burden&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/navigate?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/span&gt;,&lt;/em&gt;&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Adaptive loading with service workers</title>
    <link href="https://web.dev/adaptive-loading-with-service-workers/"/>
    <updated>2020-06-23T00:00:00Z</updated>
    <id>https://web.dev/adaptive-loading-with-service-workers/</id>
    <content type="html" mode="escaped">&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;fhqCwDP69PI&quot; videoStartAt=&quot;161&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;p&gt;Users access websites through a wide variety of devices and network connections. Even in major cities, where mobile networks are fast and reliable, one can end up experiencing slower load times, for example, when commuting in the subway, in a car, or just when moving around.
In regions like emerging markets, this phenomenon is even more common, not only due to unreliable networks, but also because devices tend to have less memory and CPU processing power.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/adaptive-loading-cds-2019/&quot;&gt;Adaptive loading&lt;/a&gt; is a web performance pattern that lets you adapt your site based on the user&#39;s network and device conditions.&lt;/p&gt;
&lt;p&gt;The adaptive loading pattern is made possible by &lt;a href=&quot;https://web.dev/service-workers-cache-storage/&quot;&gt;service workers&lt;/a&gt;, the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Network_Information_API&quot; rel=&quot;noopener&quot;&gt;Network Information API&lt;/a&gt;, the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency&quot; rel=&quot;noopener&quot;&gt;Hardware Concurrency API&lt;/a&gt;, and the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/deviceMemory&quot; rel=&quot;noopener&quot;&gt;Device Memory API&lt;/a&gt;. In this guide we explore how you can use service workers and the Network Information API to achieve an adaptive loading strategy.&lt;/p&gt;
&lt;h2 id=&quot;production-case&quot;&gt;Production case &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/adaptive-loading-with-service-workers/#production-case&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.terra.com.br/&quot; rel=&quot;noopener&quot;&gt;Terra&lt;/a&gt; is one of the biggest media companies in Brazil. It has a large user base, coming from a wide variety of devices and networks.&lt;/p&gt;
&lt;p&gt;To provide a more reliable experience to all their users, Terra combines service workers and the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Network_Information_API&quot; rel=&quot;noopener&quot;&gt;Network Information API&lt;/a&gt; to deliver lower quality images to users on 2G or 3G connections.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A screenshot of Terra&amp;#x27;s home page connected to different image qualities according to the connection type.&quot; decoding=&quot;async&quot; height=&quot;381&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 734px) 734px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EQst12doZ2b8CLO0MtO5.png?auto=format&amp;w=1468 1468w&quot; width=&quot;734&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The company also found that the scripts and assets (like banners) loaded by ad networks were especially detrimental to users navigating in 3G or slower connections.&lt;/p&gt;
&lt;p&gt;As is the case with many publishers, Terra serves &lt;a href=&quot;https://amp.dev/&quot; rel=&quot;noopener&quot;&gt;AMP&lt;/a&gt; versions of their pages to users coming from search engines and other link sharing platforms. AMP pages are usually lightweight and help mitigate the impact of ads in performance by deprioritizing their load with respect to the main content of the page.&lt;/p&gt;
&lt;p&gt;Taking that into consideration, Terra decided to start serving AMP versions of their pages not only to users coming from search engines, but also to those navigating their site in 3G connections or slower.&lt;/p&gt;
&lt;p&gt;To achieve that, they use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Network_Information_API&quot; rel=&quot;noopener&quot;&gt;Network Information API&lt;/a&gt; in the service worker to detect if the request comes from 3G or slower. If that&#39;s the case, they change the URL of the page to request the AMP version of the page instead.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A screenshot of Terra&amp;#x27;s article page connected to different image qualities according to the connection type.&quot; decoding=&quot;async&quot; height=&quot;379&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 741px) 741px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4kfDfkeIzxVXLoaTylug.png?auto=format&amp;w=1482 1482w&quot; width=&quot;741&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Thanks to this technique, they send &lt;strong&gt;70% less bytes&lt;/strong&gt; to users on slower connections. The &lt;strong&gt;time spent&lt;/strong&gt; in AMP pages is higher for 3G users and ads in AMP pages have a better &lt;strong&gt;CTR (click-through-rate)&lt;/strong&gt; for that group.&lt;/p&gt;
&lt;h2 id=&quot;implement-adaptive-loading-with-workbox&quot;&gt;Implement adaptive loading with Workbox &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/adaptive-loading-with-service-workers/#implement-adaptive-loading-with-workbox&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this section we&#39;ll explore how &lt;a href=&quot;https://web.dev/workbox/&quot;&gt;Workbox&lt;/a&gt; can be used to implement adaptive loading strategies.&lt;/p&gt;
&lt;p&gt;Workbox provides several &lt;a href=&quot;https://web.dev/runtime-caching-with-workbox/&quot;&gt;runtime caching strategies&lt;/a&gt; out of the box. They are used to indicate how the service worker generates a response after receiving a &lt;code&gt;fetch&lt;/code&gt; event.&lt;/p&gt;
&lt;p&gt;For example, in a &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#cache-first-cache-falling-back-to-network&quot; rel=&quot;noopener&quot;&gt;Cache First&lt;/a&gt; strategy the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Request&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Request&lt;/code&gt;&lt;/a&gt; will be fulfilled using the cached response (if available). If there isn&#39;t a cached response, the &lt;code&gt;Request&lt;/code&gt; will be fulfilled by a network request and the response will be cached.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;registerRoute&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;workbox-routing&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CacheFirst&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;workbox-strategies&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RegExp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/img/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CacheFirst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Caching strategies can be customized with &lt;a href=&quot;https://developer.chrome.com/docs/workbox/using-plugins/&quot; rel=&quot;noopener&quot;&gt;Workbox plugins&lt;/a&gt;. These allow you to add additional behaviors by manipulating requests and responses during the lifecycle of a request. Workbox has several built-in plugins for common cases and APIs, but you can also define a &lt;a href=&quot;https://developer.chrome.com/docs/workbox/using-plugins/#methods-for-custom-plugins&quot; rel=&quot;noopener&quot;&gt;custom plugin&lt;/a&gt;, and introduce some custom logic of your choice.&lt;/p&gt;
&lt;p&gt;To achieve adapting loading, define a custom plugin, called, for example, &lt;code&gt;adaptiveLoadingPlugin&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; adaptiveLoadingPlugin &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 function-variable function&quot;&gt;requestWillFetch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token 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; urlParts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; imageQuality&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      navigator &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;connection&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;effectiveType&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;//...&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;3g&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        imageQuality &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;q_30&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;break&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;//...&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; newUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; urlParts&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urlParts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; imageQuality&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.jpg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newRequest &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;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newUrl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&lt;span class=&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;headers&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;headers&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; newRequest&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The previous code does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Implements a &lt;code&gt;requestWillFetch()&lt;/code&gt; callback: This is called whenever a network request is about to be made, so you can alter the &lt;code&gt;Request&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Checks the connection type, by using the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency&quot; rel=&quot;noopener&quot;&gt;Network Information API&lt;/a&gt;. Based on the status of the network, it creates a new URL part, indicating the quality of the image to fetch (e.g. &lt;code&gt;q_30&lt;/code&gt; for 3G users).&lt;/li&gt;
&lt;li&gt;Creates a new URL based on the dynamic &lt;code&gt;newPart&lt;/code&gt; value, and returns the new &lt;code&gt;Request&lt;/code&gt; to be made, based on that URL.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next, pass the plugin to a &lt;code&gt;cacheFirst&lt;/code&gt; strategy containing a regular expression to match image URLs (e.g. &lt;code&gt;/img/&lt;/code&gt;):&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RegExp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/img/&#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;  workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cacheFirst&lt;/span&gt;&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 literal-property property&quot;&gt;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;images&#39;&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 literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      adaptiveLoadingPlugin&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;      workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expiration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Plugin&lt;/span&gt;&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 literal-property property&quot;&gt;maxEntries&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&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 literal-property property&quot;&gt;purgeOnQuotaError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&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 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;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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As a result, when requests for images are intercepted, the runtime caching strategy will try to fulfill the request from the cache. If it&#39;s not available, it will run the logic in the plugin, to decide which image quality to fetch from the network.&lt;/p&gt;
&lt;p&gt;Finally the response will be persisted in the cache, and sent back to the page.&lt;/p&gt;
&lt;h2 id=&quot;cloudinary-workbox-plugin&quot;&gt;Cloudinary Workbox Plugin &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/adaptive-loading-with-service-workers/#cloudinary-workbox-plugin&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cloudinary, a video and image hosting service, has a &lt;a href=&quot;https://www.npmjs.com/package/cloudinary-workbox-plugin&quot; rel=&quot;noopener&quot;&gt;Workbox Plugin&lt;/a&gt; that encapsulates the functionality explained in the previous section, making it even easier to implement.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Cloudinary and Workbox logos.&quot; decoding=&quot;async&quot; height=&quot;269&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 637px) 637px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iY2R0e4PvimaoVORo8Go.png?auto=format&amp;w=1274 1274w&quot; width=&quot;637&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The plugin is designed to work with the &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/&quot; rel=&quot;noopener&quot;&gt;Workbox webpack plugin&lt;/a&gt;. To implement it, use the &lt;a href=&quot;https://developer.chrome.com/docs/workbox/reference/workbox-webpack-plugin/#type-GenerateSW&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;GenerateSW()&lt;/code&gt;&lt;/a&gt; class:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workboxPlugin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GenerateSW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;swDest&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;sw.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;importScripts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./cloudinaryPlugin.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;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;runtimeCaching&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;urlPattern&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RegExp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;^https://res.cloudinary.com/.*/image/upload/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;CacheFirst&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;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;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;cloudinary-images&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;plugins&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function-variable function&quot;&gt;requestWillFetch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&gt;              cloudinaryPlugin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestWillFetch&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;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The previous code does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses the &lt;code&gt;GenerateSW()&lt;/code&gt; class to configure webpack to generate a service worker in the destination indicated in &lt;code&gt;swDest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Imports the cloudinary plugin script.&lt;/li&gt;
&lt;li&gt;Defines a Cache First runtime caching strategy for requests for images to the Cloudinary CDN.&lt;/li&gt;
&lt;li&gt;Passes the &lt;a href=&quot;https://www.npmjs.com/package/cloudinary-workbox-plugin&quot; rel=&quot;noopener&quot;&gt;Cloudinary Workbox Plugin&lt;/a&gt; to adjust the image quality according to the network conditions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;explore-more-adaptive-loading-strategies&quot;&gt;Explore more adaptive loading strategies &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/adaptive-loading-with-service-workers/#explore-more-adaptive-loading-strategies&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can go beyond this, by mapping device signals, like &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency&quot; rel=&quot;noopener&quot;&gt;hardware concurrency&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/deviceMemory&quot; rel=&quot;noopener&quot;&gt;device memory&lt;/a&gt; to device categories and then serving different assets depending on the device type (low-, mid- or high-end).&lt;/p&gt;
</content>
    <author>
      <name>Demian Renzulli</name>
    </author><author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>App shell UX with service workers and streams</title>
    <link href="https://web.dev/app-shell-ux-with-service-workers/"/>
    <updated>2020-06-23T00:00:00Z</updated>
    <id>https://web.dev/app-shell-ux-with-service-workers/</id>
    <content type="html" mode="escaped">&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;fhqCwDP69PI&quot; videoStartAt=&quot;438&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Single-page_application&quot; rel=&quot;noopener&quot;&gt;Single-page app (SPA)&lt;/a&gt; is an architectural pattern in which the browser runs JavaScript code to update the existing page when the user visits a different section of the site, as opposed to loading an entire new page.&lt;/p&gt;
&lt;p&gt;This means that the web app doesn&#39;t perform an actual page reload. The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/History_API&quot; rel=&quot;noopener&quot;&gt;History API&lt;/a&gt; is used instead to navigate back and forth through the user&#39;s history and manipulate the contents of the history stack.&lt;/p&gt;
&lt;p&gt;Using this type of architecture can provide an &lt;a href=&quot;https://web.dev/learn/pwa/architecture/&quot;&gt;app shell UX&lt;/a&gt; that&#39;s fast, reliable, and usually consumes less data when navigating.&lt;/p&gt;
&lt;p&gt;In multi-page apps (MPAs) each time a user navigates to a new URL, the browser progressively renders HTML specific to that page. This means a full page refresh every time you visit a new page.&lt;/p&gt;
&lt;p&gt;While both are equally valid models to use, you might want to bring some of the benefits of the app shell UX of SPAs to your existing MPA site.
In this article we&#39;ll analyze how you can achieve an SPA-like architecture in multi-page apps by combining partials, service workers, and streams.&lt;/p&gt;
&lt;h2 id=&quot;production-case&quot;&gt;Production case &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-shell-ux-with-service-workers/#production-case&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.to/&quot; rel=&quot;noopener&quot;&gt;DEV&lt;/a&gt; is a community where software developers write articles, take part in discussions, and build their professional profiles.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A screenshot of https://dev.to&quot; decoding=&quot;async&quot; height=&quot;482&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Vk2qqXg5PmLV7oCR7xVh.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Their architecture is a multi-page app based on traditional backend templating through Ruby on Rails. Their team was interested in some of the benefits of an app shell model, but didn&#39;t want to undertake a major architectural change or move away from their original tech stack.&lt;/p&gt;
&lt;p&gt;Here&#39;s how their solution works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, they create partials of their home page for the header and the footer (&lt;code&gt;shell_top.html&lt;/code&gt; and &lt;code&gt;shell_bottom.html&lt;/code&gt;) and deliver them as standalone HTML snippets with an endpoint. These assets are added to the cache at the service worker &lt;code&gt;install&lt;/code&gt; event (what&#39;s commonly referred to as &lt;a href=&quot;https://web.dev/precache-with-workbox/&quot;&gt;precaching&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;When a navigation request is intercepted by the service worker, they create a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStream&quot; rel=&quot;noopener&quot;&gt;streamed response&lt;/a&gt; by combining the cached header and footer with the main page content that just came from the server. The body is the only actual part of the page that requires fetching data from the network.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Dev&amp;#x27;s architecture consisting on static headers and footers that are cached and a body requested from the network.&quot; decoding=&quot;async&quot; height=&quot;363&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/QkGCrnzggZmrp1PXrbHb.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The key element of this solution is the usage of &lt;a href=&quot;https://developer.chrome.com/blog/sw-readablestreams/&quot; rel=&quot;noopener&quot;&gt;streams&lt;/a&gt;, which enables &lt;a href=&quot;https://streams.spec.whatwg.org/#intro&quot; rel=&quot;noopener&quot;&gt;incremental creations and updates&lt;/a&gt; of data sources. The Streams API also provides an interface for reading or writing asynchronous chunks of data, only a subset of which might be available in memory at any given time.
This way, the header of the page can be rendered as soon as it&#39;s picked from the cache, while the rest of the content is being fetched from the network. As a result, the navigation experience is so fast that users don&#39;t perceive an actual page refresh, only the new content (the body) being updated.&lt;/p&gt;
&lt;p&gt;The resulting UX is similar to the app shell UX pattern of SPAs, implemented on a MPA site.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The previous section contains a quick summary of DEV&#39;s solution. For a more detailed explanation check out their &lt;a href=&quot;https://dev.to/devteam/instant-webpages-and-terabytes-of-data-savings-through-the-magic-of-service-workers-1mkc&quot;&gt;blog post&lt;/a&gt; on this topic. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;implement-an-app-shell-ux-architecture-in-mpas-with-workbox&quot;&gt;Implement an app shell UX architecture in MPAs with Workbox &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-shell-ux-with-service-workers/#implement-an-app-shell-ux-architecture-in-mpas-with-workbox&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this section we&#39;ll cover a summary of the different parts involved in implementing an app shell UX architecture in MPAs.
For a more detailed post on how to implement this on a real site, check out &lt;a href=&quot;https://developer.chrome.com/blog/beyond-spa/&quot; rel=&quot;noopener&quot;&gt;Beyond SPAs: alternative architectures for your PWA&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-server&quot;&gt;The server &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-shell-ux-with-service-workers/#the-server&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;partials&quot;&gt;Partials &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-shell-ux-with-service-workers/#partials&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The first step is to adopt a site structure based on HTML partials. These are just modular pieces of your pages that can be reused across your site and also delivered as standalone HTML snippets.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;head partial&lt;/strong&gt; can contain all the logic needed to style and render the header of the page. The &lt;strong&gt;navbar partial&lt;/strong&gt; can contain the logic for the navigation bar, the &lt;strong&gt;footer partial&lt;/strong&gt; the code that needs to execute there, and so forth.&lt;/p&gt;
&lt;p&gt;The first time the user visits the site, your server generates a response by assembling the different parts of the page:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;app&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;routes&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;index&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&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;  res&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;headPartial &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; navbarPartial&lt;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; tag &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_TAG&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; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestData&lt;/span&gt;&lt;span class=&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;  res&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;templates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&lt;span class=&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;  res&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;footPartial&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;By using the &lt;code&gt;res&lt;/code&gt; (response) object&#39;s &lt;a href=&quot;https://nodejs.org/api/http.html#http_response_write_chunk_encoding_callback&quot; rel=&quot;noopener&quot;&gt;write() method&lt;/a&gt;, and referencing locally stored partial templates, the response can be &lt;a href=&quot;https://github.com/substack/stream-handbook&quot; rel=&quot;noopener&quot;&gt;streamed&lt;/a&gt; immediately, without getting blocked by any external data source. The browser takes this initial HTML and renders a meaningful interface and loading message right away.&lt;/p&gt;
&lt;p&gt;The next portion of the page uses API data, which involves a network request. The web app can&#39;t render anything else until it gets a response back and processes it, but at least users aren&#39;t staring at a blank screen while they wait.&lt;/p&gt;
&lt;h3 id=&quot;the-service-worker&quot;&gt;The service worker &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-shell-ux-with-service-workers/#the-service-worker&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The first time a user visits a site, the header of the page will be rendered faster, without having to wait for the body of the page. The browser still needs to go to the network to fetch the rest of the page.&lt;/p&gt;
&lt;p&gt;After the first page load, the service worker is registered, allowing you to fetch the partials for the different static parts of the page (header, navbar, footer, etc.) from the cache.&lt;/p&gt;
&lt;h4 id=&quot;precaching-static-assets&quot;&gt;Precaching static assets &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-shell-ux-with-service-workers/#precaching-static-assets&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The first step is to cache the partial HTML templates, so they are immediately available.
With &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-precaching/&quot; rel=&quot;noopener&quot;&gt;Workbox precaching&lt;/a&gt; you can store these files at the &lt;code&gt;install&lt;/code&gt; event of the service worker and keep them up to date when changes are deployed to the web app.&lt;/p&gt;
&lt;p&gt;Depending on the build process, Workbox has different solutions to generate a service worker and indicate the list of files to precache, including &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/&quot; rel=&quot;noopener&quot;&gt;webpack&lt;/a&gt; and &lt;a href=&quot;https://developers.google.com/codelabs/workbox-lab#0&quot; rel=&quot;noopener&quot;&gt;gulp&lt;/a&gt; plugins, a &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-build/&quot; rel=&quot;noopener&quot;&gt;generic node module&lt;/a&gt; and a &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-cli/&quot; rel=&quot;noopener&quot;&gt;command line interface&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For a partials configuration like the one described earlier, the resulting service worker file should contain something similar to the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;precaching&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;precacheAndRoute&lt;/span&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 literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;partials/about.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;518747aad9d7e&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;partials/foot.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;69bf746a9ecc6&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// etc.&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;h4 id=&quot;streaming&quot;&gt;Streaming &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-shell-ux-with-service-workers/#streaming&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Next, add the service worker logic so that the precached partial HTML can be sent back to the web app immediately. This is a crucial part of being reliably fast. Using the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Streams_API&quot; rel=&quot;noopener&quot;&gt;Streams API&lt;/a&gt; within our service worker makes that possible.
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/reference/workbox-streams/&quot; rel=&quot;noopener&quot;&gt;Workbox Streams&lt;/a&gt; abstracts the details of how streaming works. The package lets you pass to the library a mix of streaming sources, both from caches and runtime data that might come from the network. Workbox takes care of coordinating the individual sources and stitching them together into a single, streaming response.&lt;/p&gt;
&lt;p&gt;First, set up the strategies in Workbox to handle the different sources that will make up the streaming response.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cacheStrategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cacheFirst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;core&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cacheNames&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;precache&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; apiStrategy &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;staleWhileRevalidate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;API_CACHE_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expiration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Plugin&lt;/span&gt;&lt;span class=&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;maxEntries&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;The first strategy reads data that&#39;s been precached, like the partial HTML templates.&lt;/li&gt;
&lt;li&gt;The second strategy implements the &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#stale-while-revalidate&quot; rel=&quot;noopener&quot;&gt;stale-while-revalidate&lt;/a&gt; caching logic, along with &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-expiration/#restrict-the-number-of-cache-entries&quot; rel=&quot;noopener&quot;&gt;least-recently-used cache expiration&lt;/a&gt; logic once we reach 50 entries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next, tell Workbox how to use the strategies to construct a complete, streaming response, by passing in an array of sources as functions to execute immediately:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;streams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;strategy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; cacheStrategy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCacheKeyForURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/head.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; cacheStrategy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCacheKeyForURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/navbar.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&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; tag &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&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;tag&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_TAG&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; listResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; apiStrategy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&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; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; listResponse&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;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; templates&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; cacheStrategy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCacheKeyForURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/foot.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;The first two sources are precached partial templates read directly from the service worker&#39;s cache, so they&#39;ll always be available immediately.&lt;/li&gt;
&lt;li&gt;The next source function fetches data from the network, and processes the response into the HTML that the web app expects.&lt;/li&gt;
&lt;li&gt;Finally, a cached copy of the footer and closing HTML tags is streamed to complete the response.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Workbox takes the result from each source and streams it to the web app, in sequence, only delaying if the next function in the array hasn&#39;t completed yet.
As a result, the user immediately sees the page being painted. The experience is so fast that when navigating the header stays in its position without making the user perceive the full page refresh. This is very similar to the UX that the app shell SPA model provides.&lt;/p&gt;
</content>
    <author>
      <name>Demian Renzulli</name>
    </author><author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Instant navigation experiences</title>
    <link href="https://web.dev/instant-navigation-experiences/"/>
    <updated>2020-06-23T00:00:00Z</updated>
    <id>https://web.dev/instant-navigation-experiences/</id>
    <content type="html" mode="escaped">&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;fhqCwDP69PI&quot; videoStartAt=&quot;285&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;p&gt;Performing a task on a site commonly involves several steps. For example, purchasing a product in an e-commerce website might involve searching for a product, picking an item from the list of results, adding the item to the cart, and completing the operation by checking out.&lt;/p&gt;
&lt;p&gt;In technical terms, moving through different pages means making a &lt;strong&gt;navigation request&lt;/strong&gt;. As a general rule, you &lt;strong&gt;don&#39;t&lt;/strong&gt; want to use long-lived &lt;code&gt;Cache-Control&lt;/code&gt; headers to cache the HTML response for a navigation request. They should normally be satisfied via the network, with &lt;code&gt;Cache-Control: no-cache&lt;/code&gt;, to ensure that the HTML, along with the chain of subsequent network requests, is (reasonably) fresh.
Having to go against the network each time the user navigates to a new page unfortunately means that each navigation might be slow—at the very least, it means that it won&#39;t be &lt;em&gt;reliably&lt;/em&gt; fast.&lt;/p&gt;
&lt;p&gt;To speed up these requests, if you can anticipate the user&#39;s action, you can request these pages and assets beforehand and keep them in the cache for a short period of time until the user clicks on these links. This technique is called &lt;a href=&quot;https://web.dev/link-prefetch/&quot;&gt;prefetching&lt;/a&gt; and it&#39;s commonly implemented by adding &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt; tags to pages, indicating the resource to prefetch.&lt;/p&gt;
&lt;p&gt;In this guide we&#39;ll explore different ways in which &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Service_Worker_API&quot; rel=&quot;noopener&quot;&gt;service workers&lt;/a&gt; can be used as a complement of traditional prefetching techniques.&lt;/p&gt;
&lt;h2 id=&quot;production-cases&quot;&gt;Production cases &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/instant-navigation-experiences/#production-cases&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mercadolibre.com.ar/&quot; rel=&quot;noopener&quot;&gt;MercadoLibre&lt;/a&gt; is the biggest e-commerce site in Latin America. To speed up navigations, they dynamically inject &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt; tags in some parts of the flow. For example, in listing pages, they fetch the next result page as soon as the user scrolls to the bottom of the listing:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Screenshot of MercadoLibre&amp;#x27;s listing pages one and two and a Link Prefetch tag connecting both.&quot; decoding=&quot;async&quot; height=&quot;397&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 682px) 682px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/80D6QavdktSNb6xnhXE0.png?auto=format&amp;w=1364 1364w&quot; width=&quot;682&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Prefetched files are requested at the &amp;quot;Lowest&amp;quot; priority and stored in the &lt;a href=&quot;https://web.dev/http-cache/&quot;&gt;HTTP cache&lt;/a&gt; or the &lt;a href=&quot;https://calendar.perfplanet.com/2016/a-tale-of-four-caches/&quot; rel=&quot;noopener&quot;&gt;memory cache&lt;/a&gt; (depending on whether the resource is cacheable or not), for an amount of time that varies by browsers. For example, as of Chrome 85, this value is 5 minutes. Resources are kept around for five minutes, after which the normal &lt;code&gt;Cache-Control&lt;/code&gt; rules for the resource apply.&lt;/p&gt;
&lt;p&gt;Using service worker caching can help you extend the lifetime of prefetch resources beyond the five-minute window.&lt;/p&gt;
&lt;p&gt;For example, Italian sports portal &lt;a href=&quot;https://sport.virgilio.it/&quot; rel=&quot;noopener&quot;&gt;Virgilio Sport&lt;/a&gt; uses service workers to prefetch the most popular posts in their home page. They also use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Network_Information_API&quot; rel=&quot;noopener&quot;&gt;Network Information API&lt;/a&gt; to avoid prefetching for users that are on a 2G connection.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Virgilio Sport logo.&quot; decoding=&quot;async&quot; height=&quot;100&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 340px) 340px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqiSoliDKZ9SR1NX2Ek3.png?auto=format&amp;w=680 680w&quot; width=&quot;340&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;As a result of this, over 3 weeks of observation Virgilio Sport witnessed load times for navigation to articles improve &lt;strong&gt;78%&lt;/strong&gt;, and the number of article impressions increase &lt;strong&gt;45%&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A screenshot of Virgilio Sport home and article pages, with impact metrics after prefetching.&quot; decoding=&quot;async&quot; height=&quot;442&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 536px) 536px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wn7OR4CA21QJUYhs8OUu.png?auto=format&amp;w=1072 1072w&quot; width=&quot;536&quot; /&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;implement-precaching-with-workbox&quot;&gt;Implement precaching with Workbox &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/instant-navigation-experiences/#implement-precaching-with-workbox&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the following section we&#39;ll use &lt;a href=&quot;https://web.dev/workbox/&quot;&gt;Workbox&lt;/a&gt; to show how to implement different caching techniques in the service worker that can be used as a complement to &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt;, or even a replacement for it, by delegating this task completely to the service worker.&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; You must take steps to ensure that adding a service worker to your site doesn&#39;t end up actually slowing down your navigations. Starting up the service worker without using it to respond to a navigation request will introduce a small amount of latency (as explained in &lt;a href=&quot;https://www.youtube.com/watch?v=25aCD5XL1Jk&quot;&gt;Building Faster, More Resilient Apps with Service Workers&lt;/a&gt;). You can mitigate this overhead by enabling a feature called &lt;a href=&quot;https://developer.chrome.com/blog/navigation-preload/&quot;&gt;navigation preload&lt;/a&gt;, and then using the &lt;a href=&quot;https://developer.chrome.com/blog/navigation-preload/#using-the-preloaded-response&quot;&gt;network response&lt;/a&gt; that&#39;s been preloaded inside of your fetch event handler. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;1-precache-static-pages-and-page-subresources&quot;&gt;1. Precache static pages and page subresources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/instant-navigation-experiences/#1-precache-static-pages-and-page-subresources&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/precache-with-workbox/&quot;&gt;Precaching&lt;/a&gt; is the ability of the service worker to save files to the cache while it&#39;s installing.&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; Precaching sounds similar to prefetching, but it&#39;s a different technique. In the first one, the service worker fetches and stores resources (typically static files) while it&#39;s installing and keeps them in the cache until a new version of the file is available. In the second, resources are requested ahead of time to have it in the cache for brief periods of time in order to speed up subsequent navigations. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;In the following cases precaching is used to achieve a similar goal as prefetching: making navigations faster.&lt;/p&gt;
&lt;h4 id=&quot;precaching-static-pages&quot;&gt;Precaching static pages &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/instant-navigation-experiences/#precaching-static-pages&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;For pages that are generated at build time (e.g. &lt;code&gt;about.html&lt;/code&gt;, &lt;code&gt;contact.html&lt;/code&gt;), or in completely static sites, one can just add the site&#39;s documents to the precache list, so they are already available in the cache every time the user accesses them:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;precaching&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;precacheAndRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/about.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;abcd1234&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// ... other entries ...&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;h4 id=&quot;precaching-page-subresources&quot;&gt;Precaching page subresources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/instant-navigation-experiences/#precaching-page-subresources&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Precaching static assets that the different sections of the site might use (e.g. JavaScript, CSS, etc.), is a general best practice and can give an extra boost in prefetching scenarios.&lt;/p&gt;
&lt;p&gt;To speed up navigations in an e-commerce site, you can use  &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt; tags in listing pages to prefetch product detail pages for the first few products of a listing page. If you have already precached the product page subresources, this can make the navigation even faster.&lt;/p&gt;
&lt;p&gt;To implement this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt; tag to the page:&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;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;prefetch&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;/phones/smartphone-5x.html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;document&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;ul&gt;
&lt;li&gt;Add the page subresources to the precache list in the service worker:&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;precaching&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;precacheAndRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;/styles/product-page.ac29.css&#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;// ... other entries ...&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;2-extend-the-lifetime-of-prefetch-resources&quot;&gt;2. Extend the lifetime of prefetch resources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/instant-navigation-experiences/#2-extend-the-lifetime-of-prefetch-resources&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt; fetches and keeps resources in the HTTP cache for a limited amount of time, after which point the &lt;code&gt;Cache-Control&lt;/code&gt; rules for a resource apply. As of Chrome 85, this value is 5 minutes.&lt;/p&gt;
&lt;p&gt;Service workers allow you to extend the lifetime of the prefetch pages, while providing the added benefit of making those resources available for offline usage.&lt;/p&gt;
&lt;p&gt;In the previous example, one could complement the &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt; used to prefetch a product page with a &lt;a href=&quot;https://web.dev/runtime-caching-with-workbox/&quot;&gt;Workbox runtime caching strategy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To implement that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt; tag to the page:&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;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;prefetch&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;/phones/smartphone-5x.html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;document&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;ul&gt;
&lt;li&gt;Implement a runtime caching strategy in the service worker for these types of requests:&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StaleWhileRevalidate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;document-cache&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expiration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;maxAgeSeconds&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 30 Days&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In this case, we have opted to use a &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#stale-while-revalidate&quot; rel=&quot;noopener&quot;&gt;stale-while-revalidate strategy&lt;/a&gt;. In this strategy, pages can be requested from both the cache and the network, in parallel. The response comes from the cache if available, otherwise from the network. The cache is always kept up to date with the network response with each successful request.&lt;/p&gt;
&lt;h3 id=&quot;3-delegate-prefetching-to-the-service-worker&quot;&gt;3. Delegate prefetching to the service worker &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/instant-navigation-experiences/#3-delegate-prefetching-to-the-service-worker&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In most cases the best approach is to use &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt;. The tag is a &lt;a href=&quot;https://www.w3.org/TR/resource-hints/&quot; rel=&quot;noopener&quot;&gt;resource hint&lt;/a&gt; designed to make prefetching as efficient as possible.&lt;/p&gt;
&lt;p&gt;In some cases, though, it might be better to delegate this task completely to the service worker.
For example: to prefetch the first few products in a client-side rendered product listing page, one might need to inject several &lt;code&gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&lt;/code&gt; tags dynamically in the page, based on an API response. This can momentarily consume time on the page&#39;s main thread and make the implementation more difficult.&lt;/p&gt;
&lt;p&gt;In cases like this, use a &amp;quot;page to service worker communication strategy&amp;quot;, to delegate the task of prefetching completely to the service worker. This type of communication can be achieved by using &lt;a href=&quot;https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage&quot; rel=&quot;noopener&quot;&gt;worker.postMessage()&lt;/a&gt;:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;An icon of a page making two way communication with a service worker.&quot; decoding=&quot;async&quot; height=&quot;205&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 626px) 626px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vokHySREOo6Y3PpxzxRC.png?auto=format&amp;w=1252 1252w&quot; width=&quot;626&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-window/&quot; rel=&quot;noopener&quot;&gt;Workbox Window package&lt;/a&gt; simplifies this type of communication, abstracting many details of the underlying call being done.&lt;/p&gt;
&lt;p&gt;Prefetching with Workbox Window can be implemented in the following way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In the page: call the service worker passing it the type of message, and the list of URLs to prefetch:&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wb &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;Workbox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/sw.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;br /&gt;wb&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 punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prefetchResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; wb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;messageSW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;PREFETCH_URLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;urls&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;…&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token 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;ul&gt;
&lt;li&gt;In the service worker: implement a message handler to issue a &lt;code&gt;fetch()&lt;/code&gt; request for each URL to prefetch:&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&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;message&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&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;PREFETCH_URLS&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Fetch URLs and store them in the cache&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</content>
    <author>
      <name>Demian Renzulli</name>
    </author><author>
      <name>Jeff Posnick</name>
    </author><author>
      <name>Gilberto Cocchi</name>
    </author>
  </entry>
  
  <entry>
    <title>Resilient search experiences</title>
    <link href="https://web.dev/resilient-search-experiences/"/>
    <updated>2020-06-23T00:00:00Z</updated>
    <id>https://web.dev/resilient-search-experiences/</id>
    <content type="html" mode="escaped">&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;fhqCwDP69PI&quot; videoStartAt=&quot;35&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;p&gt;Even in locations with fast networks a user might lose connection or connect to a flaky network, at some moments of the day.
For example: a user is on the subway searching on the phone for a product on an e-commerce website. They type the product name, click the &amp;quot;search&amp;quot; button, and while waiting for the results, the connection is lost, leading to the standard browser offline page.&lt;/p&gt;
&lt;p&gt;As a result, unless the user decides to come back to the site later, and repeat the same task, the site might lose a potential transaction and customer.&lt;/p&gt;
&lt;p&gt;To provide a more resilient search experience in these cases you can use the &lt;a href=&quot;https://developer.chrome.com/blog/background-sync/&quot; rel=&quot;noopener&quot;&gt;Background Sync API&lt;/a&gt;, which persists failed queries so they can be retried once the connection is recovered. This technique, in combination with &lt;a href=&quot;https://web.dev/push-notifications/&quot;&gt;Web Push Notifications&lt;/a&gt; lets you inform the user of the search results, allowing you to keep them engaged with your service.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-quaternary-box-bg color-quaternary-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; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Code brackets&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;M9.41 16.59L8 18l-6-6 6-6 1.41 1.41L4.83 12l4.58 4.59zm5.18-9.18L16 6l6 6-6 6-1.41-1.41L19.17 12l-4.58-4.59z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Try it&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Try the &lt;a href=&quot;https://web.dev/codelab-building-resilient-search-experiences&quot;&gt;Building resilient search experiences with Workbox&lt;/a&gt; for a hands-on demonstration of the ideas explained in this guide. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;production-case&quot;&gt;Production case &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/resilient-search-experiences/#production-case&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For concrete application of this technique let&#39;s take a look at Google Search for Chrome in Android.
When visiting the Google Search web app and going offline, instead of showing the standard network error page, the site serves a custom offline response, but allows users to enter their search query immediately.
The page also prompts the user to opt-in for notifications, to receive a link to the search results page once the connection is recovered.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A screenshot of the background retry interface in Google Search.&quot; decoding=&quot;async&quot; height=&quot;475&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 257px) 257px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqDtqgbKOsxRFnr2lNSy.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqDtqgbKOsxRFnr2lNSy.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqDtqgbKOsxRFnr2lNSy.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqDtqgbKOsxRFnr2lNSy.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqDtqgbKOsxRFnr2lNSy.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqDtqgbKOsxRFnr2lNSy.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqDtqgbKOsxRFnr2lNSy.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqDtqgbKOsxRFnr2lNSy.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqDtqgbKOsxRFnr2lNSy.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqDtqgbKOsxRFnr2lNSy.png?auto=format&amp;w=514 514w&quot; width=&quot;257&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;When the user performs a search, the service worker allows the query to be deferred and sent to Google&#39;s servers as soon as the device goes back online by using the &lt;a href=&quot;https://developer.chrome.com/blog/background-sync/&quot; rel=&quot;noopener&quot;&gt;Background Sync API&lt;/a&gt;, and to inform the user of the result by 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;.&lt;/p&gt;
&lt;img alt=&quot;A screenshot of the offline flow in Google Search.&quot; decoding=&quot;async&quot; height=&quot;436&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/ZZItVQMLUPmVbwJlfDck.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Service workers allow Google Search to provide a &lt;a href=&quot;https://web.dev/google-search-sw/#meaningful-offline-experience&quot;&gt;meaningful offline experience&lt;/a&gt; and keep the user engaged, letting them complete their task.&lt;/p&gt;
&lt;h2 id=&quot;implement-resilient-search-experiences-with-workbox&quot;&gt;Implement resilient search experiences with Workbox &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/resilient-search-experiences/#implement-resilient-search-experiences-with-workbox&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While Google Search implements this functionality without using Workbox, the &lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox library&lt;/a&gt; makes it easier by providing a &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-background-sync/&quot; rel=&quot;noopener&quot;&gt;Background Sync module&lt;/a&gt;, which takes care of many implementation details for us.&lt;/p&gt;
&lt;img alt=&quot;A service worker and a cache object communicating with each other.&quot; decoding=&quot;async&quot; height=&quot;383&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/X06meG8U60SABUabxwHb.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;To implement a resilient search experience in Workbox, first, import the following modules in your service worker:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;BackgroundSyncPlugin&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;workbox-background-sync&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;registerRoute&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;workbox-routing&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;NetworkOnly&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;workbox-strategies&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Next, create an instance of the &lt;a href=&quot;https://developer.chrome.com/docs/workbox/reference/workbox-background-sync/#type-BackgroundSyncPlugin&quot; rel=&quot;noopener&quot;&gt;workbox.backgroundSync plugin&lt;/a&gt;, to automatically add failed requests to a queue, so they can be retried later:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; bgSyncPlugin &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;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;backgroundSync&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;offlineQueryQueue&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;maxRetentionTime&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function-variable function&quot;&gt;onSync&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token 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;let&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shiftRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;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; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;offline-search-responses&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; offlineUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;amp;notification=true&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;offlineUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;showNotification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;offlineUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unshiftRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The plugin receives the following parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;offlineQueryQueue&lt;/code&gt;: The name of the queue that will be used to persist the failed requests in &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IndexedDB_API&quot; rel=&quot;noopener&quot;&gt;IndexedDB&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxRetentionTime&lt;/code&gt;: The amount of time in minutes a request may be retried, after which point they will be discarded.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onSync&lt;/code&gt;: The callback that will be triggered when the connection is recovered. At that point, each failed request can be dequeued and processed, by calling &lt;code&gt;queue.shiftRequest()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, define a &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#network-only&quot; rel=&quot;noopener&quot;&gt;networkOnly&lt;/a&gt; runtime caching strategy for requests to the search URL (e.g. &lt;code&gt;/search_action&lt;/code&gt;) and pass it the &lt;code&gt;bgSyncPlugin&lt;/code&gt; defined previously:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  matchSearchUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NetworkOnly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;plugins&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;bgSyncPlugin&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This tells Workbox to always go to the network when the service worker intercepts a request for the search endpoint, and to delegate to the Background Sync plugin the task of managing offline scenarios.&lt;/p&gt;
&lt;p&gt;As a result, when the user goes offline while searching, the query is automatically saved. When the connection is recovered the offline logic is triggered to process the request and inform the user of the result.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/resilient-search-experiences/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this article you learned how to implement a search experience capable of responding gracefully to offline scenarios, by combining the &lt;a href=&quot;https://developer.chrome.com/blog/background-sync/&quot; rel=&quot;noopener&quot;&gt;Background Sync API&lt;/a&gt; and 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;.
We used Workbox to show how to implement this feature, as it simplifies the process, but the same can be achieved by writing vanilla service worker code.&lt;/p&gt;
&lt;p&gt;In the code samples we focused on the core part of the feature: how requests are intercepted and managed by the service worker. For a step-by-step guide on how to implement this functionality, including the offline page and the notification logic, check out the codelab at the end of this article.&lt;/p&gt;
</content>
    <author>
      <name>Demian Renzulli</name>
    </author><author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Building resilient search experiences with Workbox</title>
    <link href="https://web.dev/codelab-building-resilient-search-experiences/"/>
    <updated>2020-06-23T00:00:00Z</updated>
    <id>https://web.dev/codelab-building-resilient-search-experiences/</id>
    <content type="html" mode="escaped">&lt;p&gt;This codelab shows you how to implement a resilient search experience with Workbox. The demo app it uses contains a search box that calls a server endpoint, and redirects the user to a basic HTML page.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This codelab uses &lt;a href=&quot;https://www.google.com/chrome/&quot;&gt;Chrome DevTools&lt;/a&gt;. Download Chrome if you don&#39;t already have it. &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-building-resilient-search-experiences/#measure&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before adding optimizations, it&#39;s always a good idea to first analyze the current state of the application.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Remix to Edit&lt;/strong&gt; to make the project editable.&lt;/li&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;/ul&gt;
&lt;p&gt;In the new tab that just opened, check how the website behaves when going offline:&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;Network&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Open Chrome DevTools and select the Network panel.&lt;/li&gt;
&lt;li&gt;In the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/network/reference/#throttling&quot; rel=&quot;noopener&quot;&gt;Throttling drop-down list&lt;/a&gt;, select &lt;strong&gt;Offline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the demo app enter a search query, then click the &lt;strong&gt;Search&lt;/strong&gt; button.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The standard browser error page is shown:&lt;/p&gt;
&lt;img alt=&quot;A screenshot of the default offline UX in the browser.&quot; decoding=&quot;async&quot; height=&quot;465&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/g4Naxj1RnipuqxqzC62x.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;provide-a-fallback-response&quot;&gt;Provide a fallback response &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-building-resilient-search-experiences/#provide-a-fallback-response&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The service worker contains the code to add the offline page to the &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-precaching/#explanation-of-the-precache-list&quot; rel=&quot;noopener&quot;&gt;precache list&lt;/a&gt;, so it can always be cached at the service worker &lt;code&gt;install&lt;/code&gt; event.&lt;/p&gt;
&lt;p&gt;Usually you would need to instruct Workbox to add this file to the precache list at build time, by integrating the library with your build tool of choice (e.g. &lt;a href=&quot;https://webpack.js.org/&quot; rel=&quot;noopener&quot;&gt;webpack&lt;/a&gt; or &lt;a href=&quot;https://gulpjs.com/&quot; rel=&quot;noopener&quot;&gt;gulp&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;For simplicity, we&#39;ve already done it for you. The following code at &lt;code&gt;public/sw.js&lt;/code&gt; does that:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;FALLBACK_HTML_URL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/index_offline.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;…&lt;br /&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;precaching&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;precacheAndRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FALLBACK_HTML_URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; To learn more about how to integrate Workbox with build tools, check out the &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/&quot;&gt;webpack Workbox plugin&lt;/a&gt; and the &lt;a href=&quot;https://developers.google.com/codelabs/workbox-lab#0&quot;&gt;Gulp Workbox plugin&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Next, add code to use the offline page as a fallback response:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To view the source, press &lt;strong&gt;View Source&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Add the following code to the bottom of &lt;code&gt;public/sw.js&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setDefaultHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&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;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NetworkOnly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setCatchHandler&lt;/span&gt;&lt;span class=&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;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token 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;switch&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;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;document&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FALLBACK_HTML_URL&lt;/span&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;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Response&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 punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The Glitch UI says &lt;code&gt;workbox is not defined&lt;/code&gt; because it doesn&#39;t realize that the &lt;code&gt;importScripts()&lt;/code&gt; call on line 1 is importing the library. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;The code does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Defines a default &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#network-only&quot; rel=&quot;noopener&quot;&gt;Network Only strategy&lt;/a&gt; that will apply to all requests.&lt;/li&gt;
&lt;li&gt;Declares a global error handler, by calling &lt;code&gt;workbox.routing.setCatchHandler()&lt;/code&gt; to manage failed requests. When requests are for documents, a fallback offline HTML page will be returned.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To test this functionality:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go back to the other tab that is running your app.&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Throttling&lt;/strong&gt; drop-down list back to &lt;strong&gt;Online&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Press Chrome&#39;s &lt;strong&gt;Back&lt;/strong&gt; button to navigate back to the search page.&lt;/li&gt;
&lt;li&gt;Make sure that the &lt;strong&gt;Disable cache&lt;/strong&gt; checkbox in DevTools is disabled.&lt;/li&gt;
&lt;li&gt;Long-press Chrome&#39;s &lt;strong&gt;Reload&lt;/strong&gt; button and select
&lt;a href=&quot;https://stackoverflow.com/q/14969315/1669860&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Empty cache and hard reload&lt;/strong&gt;&lt;/a&gt;
to ensure that your service worker is updated.&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Throttling&lt;/strong&gt; drop-down list back to &lt;strong&gt;Offline&lt;/strong&gt; again.&lt;/li&gt;
&lt;li&gt;Enter a search query, and click the &lt;strong&gt;Search&lt;/strong&gt; button again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The fallback HTML page is shown:&lt;/p&gt;
&lt;img alt=&quot;A screenshot of the custom offline UX in the browser.&quot; decoding=&quot;async&quot; height=&quot;456&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/2o0feM6Ib4GnLdKQqV9G.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;request-notification-permission&quot;&gt;Request notification permission &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-building-resilient-search-experiences/#request-notification-permission&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For simplicity, the offline page at &lt;code&gt;views/index_offline.html&lt;/code&gt; already contains the code to request notification permissions in a script block at the bottom:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestNotificationPermission&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&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 class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;showOfflineText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The code does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When the user clicks &lt;strong&gt;subscribe to notifications&lt;/strong&gt; the &lt;code&gt;requestNotificationPermission()&lt;/code&gt; function is called, which calls &lt;code&gt;Notification.requestPermission()&lt;/code&gt;, to show the default browser permission prompt. The promise resolves with the permission picked by the user, which can be either &lt;code&gt;granted&lt;/code&gt;, &lt;code&gt;denied&lt;/code&gt;, or &lt;code&gt;default&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Passes the resolved permission to  &lt;code&gt;showOfflineText()&lt;/code&gt; to show the appropriate text to the user.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;persist-offline-queries-and-retry-when-back-online&quot;&gt;Persist offline queries and retry when back online &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-building-resilient-search-experiences/#persist-offline-queries-and-retry-when-back-online&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next, implement &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-background-sync/&quot; rel=&quot;noopener&quot;&gt;Workbox Background Sync&lt;/a&gt; to persist offline queries, so they can be retried when the browser detects that connectivity has returned.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;public/sw.js&lt;/code&gt; for edit.&lt;/li&gt;
&lt;li&gt;Add the following code at the end of the file:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; bgSyncPlugin &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;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;backgroundSync&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;offlineQueryQueue&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;maxRetentionTime&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function-variable function&quot;&gt;onSync&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token 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;let&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shiftRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;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; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;offline-search-responses&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; offlineUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;amp;notification=true&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;offlineUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;showNotification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;offlineUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unshiftRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The code does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;workbox.backgroundSync.Plugin&lt;/code&gt; contains the logic to add failed requests to a queue so they can be retried later. These requests will be persisted in &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IndexedDB_API&quot; rel=&quot;noopener&quot;&gt;IndexedDB&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;maxRetentionTime&lt;/code&gt; indicates the amount of time a request may be retried. In this case we have chosen 60 minutes (after which it will be discarded).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onSync&lt;/code&gt; is the most important part of this code. This callback will be called when connection is back so that queued requests are retrieved and then fetched from the network.&lt;/li&gt;
&lt;li&gt;The network response is added to the &lt;code&gt;offline-search-responses&lt;/code&gt; cache, appending the &lt;code&gt;&amp;amp;notification=true&lt;/code&gt; query param, so that this cache entry can be picked up when a user clicks on the notification.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To integrate background sync with your service, define a &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#network-only&quot; rel=&quot;noopener&quot;&gt;NetworkOnly&lt;/a&gt; strategy for requests to the search URL (&lt;code&gt;/search_action&lt;/code&gt;) and pass the previously defined &lt;code&gt;bgSyncPlugin&lt;/code&gt;. Add the following code to the bottom of &lt;code&gt;public/sw.js&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;matchSearchUrl&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&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; notificationParam &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&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;notification&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/search_action&#39;&lt;/span&gt; &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;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;notificationParam &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;true&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  matchSearchUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NetworkOnly&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;plugins&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;bgSyncPlugin&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This tells Workbox to always go to the network, and, when requests fail, use the background sync logic.&lt;/p&gt;
&lt;p&gt;Next, add the following code to the bottom of &lt;code&gt;public/sw.js&lt;/code&gt; to define a caching strategy for requests coming from notifications. Use a &lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#cache-first-cache-falling-back-to-network&quot; rel=&quot;noopener&quot;&gt;CacheFirst&lt;/a&gt; strategy, so they can be served from the cache.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;matchNotificationUrl&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&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; notificationParam &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&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;notification&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/search_action&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;notificationParam &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;true&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matchNotificationUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CacheFirst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;     &lt;span class=&quot;token literal-property property&quot;&gt;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;offline-search-responses&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;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;Finally, add the code to show notifications:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&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;span class=&quot;token parameter&quot;&gt;notificationUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Notification&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;permission&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&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;span class=&quot;token string&quot;&gt;&#39;Your search is ready!&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Click to see you search result&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;icon&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/img/workbox.jpg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;data&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 literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; notificationUrl&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token 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;self&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;notificationclick&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  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;br /&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;&lt;br /&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;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;notification&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;test-the-feature&quot;&gt;Test the feature &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-building-resilient-search-experiences/#test-the-feature&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Go back to the other tab that is running your app.&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Throttling&lt;/strong&gt; drop-down list back to &lt;strong&gt;Online&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Press Chrome&#39;s &lt;strong&gt;Back&lt;/strong&gt; button to navigate back to the search page.&lt;/li&gt;
&lt;li&gt;Long-press Chrome&#39;s &lt;strong&gt;Reload&lt;/strong&gt; button and select
&lt;a href=&quot;https://stackoverflow.com/q/14969315/1669860&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;Empty cache and hard reload&lt;/strong&gt;&lt;/a&gt;
to ensure that your service worker is updated.&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Throttling&lt;/strong&gt; drop-down list back to &lt;strong&gt;Offline&lt;/strong&gt; again.&lt;/li&gt;
&lt;li&gt;Enter a search query, and click the &lt;strong&gt;Search&lt;/strong&gt; button again.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;subscribe to notifications&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;When Chrome asks you if you want to grant the app permission to send notifications,
click &lt;strong&gt;Allow&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter another search query and click the &lt;strong&gt;Search&lt;/strong&gt; button again.&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Throttling&lt;/strong&gt; drop-down list back to &lt;strong&gt;Online&lt;/strong&gt; again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once the connection is back a notification will be shown:&lt;/p&gt;
&lt;img alt=&quot;A screenshot of the full offline flow.&quot; decoding=&quot;async&quot; height=&quot;315&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/kvnl2PlazBdppGF4eMi0.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-building-resilient-search-experiences/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Workbox provides many built-in features to make your PWAs more resilient and engaging.
In this codelab you have explored how to implement the Background Sync API by way of the Workbox abstraction, to ensure that offline user queries are not lost, and can be retried once connection is back.
The demo is a simple search app, but you can use a similar implementation for more complex scenarios and use cases, including chat apps, posting messages on a social network, etc.&lt;/p&gt;
</content>
    <author>
      <name>Demian Renzulli</name>
    </author><author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Preparing for AppCache removal</title>
    <link href="https://web.dev/appcache-removal/"/>
    <updated>2020-05-18T00:00:00Z</updated>
    <id>https://web.dev/appcache-removal/</id>
    <content type="html" mode="escaped">&lt;p&gt;Following up on &lt;a href=&quot;https://blog.chromium.org/2020/01/appcache-scope-restricted.html&quot; rel=&quot;noopener&quot;&gt;previous announcements&lt;/a&gt;, support for &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/applicationCache&quot; rel=&quot;noopener&quot;&gt;AppCache&lt;/a&gt; will be removed from Chrome and other Chromium-based browsers. We encourage developers to migrate off of AppCache now, rather than waiting any longer.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/docs/workbox/service-worker-overview/&quot; rel=&quot;noopener&quot;&gt;Service workers&lt;/a&gt;,
which are widely supported in current browsers, offer an alternative to providing the offline
experience that AppCache had offered. See &lt;a href=&quot;https://web.dev/appcache-removal/#migration-strategies&quot;&gt;Migration strategies&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;timeline&quot;&gt;Timeline &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#timeline&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.chromium.org/2020/03/chrome-and-chrome-os-release-updates.html&quot; rel=&quot;noopener&quot;&gt;Recent changes&lt;/a&gt; to the Chrome release schedule means that the timing of some of these steps may vary. We will attempt to keep this timeline up-to-date, but at this point, please migrate off of AppCache as soon as possible, instead of waiting for specific milestones.&lt;/p&gt;
&lt;p&gt;A &amp;quot;deprecated&amp;quot; feature still exists, but logs warning messages discouraging use. A &amp;quot;removed&amp;quot; feature no longer exists in the browser.&lt;/p&gt;
&lt;div&gt;
  &lt;table&gt;
    &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://groups.google.com/a/chromium.org/g/blink-dev/c/UKF8cK0EwMI/m/NLhsIrs-AQAJ&quot;&gt;Deprecation in non-secure contexts&lt;/a&gt;
    &lt;/td&gt;
    &lt;td&gt;Chrome 50 (April 2016)
    &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://groups.google.com/a/chromium.org/g/blink-dev/c/ANnafFBhReY/m/1Xdr53KxBAAJ?pli=1&quot;&gt;Removal from non-secure contexts&lt;/a&gt;
    &lt;/td&gt;
    &lt;td&gt;Chrome 70 (October 2018)
    &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://groups.google.com/a/chromium.org/g/blink-dev/c/FvM-qo7BfkI/m/0daqyD8kCQAJ&quot;&gt;Deprecation in secure contexts&lt;/a&gt;
    &lt;/td&gt;
    &lt;td&gt;Chrome 79 (December 2019)
    &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://blog.chromium.org/2020/01/appcache-scope-restricted.html&quot;&gt;AppCache scope restriction&lt;/a&gt;
    &lt;/td&gt;
    &lt;td&gt;Chrome 80 (February 2020)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
    &lt;td&gt;&quot;Reverse&quot; origin trial begins
    &lt;/td&gt;
    &lt;td&gt;Chrome 84 (July 2020)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
    &lt;td&gt;&lt;a href=&quot;https://groups.google.com/a/chromium.org/g/blink-dev/c/FvM-qo7BfkI/m/AvxoE6JpBgAJ&quot;&gt;Removal from secure contexts&lt;/a&gt;, except for those opted-in to the origin trial
    &lt;/td&gt;
    &lt;td&gt;Chrome 85 (August 2020)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
    &lt;td&gt;Complete removal from secure contexts for everyone, with completion of origin trial
    &lt;/td&gt;
    &lt;td&gt;October 5th, 2021 (roughly Chrome 95)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This timeline applies to Chrome on &lt;strong&gt;all platforms other than iOS&lt;/strong&gt;. There is also an adjusted timeline for AppCache used within an Android &lt;a href=&quot;https://developer.android.com/reference/android/webkit/WebView&quot;&gt;WebView&lt;/a&gt;. For more info, see &lt;a href=&quot;https://web.dev/appcache-removal/#the-cross-platform-story&quot;&gt;The cross-platform story&lt;/a&gt; later in this post. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;origin-trial&quot;&gt;Origin trial &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#origin-trial&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The timeline lists two upcoming milestones for removal. Beginning with Chrome 85, AppCache will no longer be available in Chrome by default. Developers who require additional time to migrate off of AppCache can &lt;a href=&quot;https://developers.chrome.com/origintrials/#/register_trial/1776670052997660673&quot; rel=&quot;noopener&quot;&gt;sign up&lt;/a&gt; for a &amp;quot;reverse&amp;quot; &lt;a href=&quot;https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md&quot; rel=&quot;noopener&quot;&gt;origin trial&lt;/a&gt; to extend the availability of AppCache for their web apps. The origin trial will start in Chrome 84 (in advance of the default removal in Chrome 85), and will be active through October 5th, 2021 (roughly Chrome 95). At that point, AppCache will be fully removed for everyone, even those who had signed up for the origin trial.&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; Why are we calling this a &amp;quot;reverse&amp;quot; origin trial? Normally, an origin trial allows developers to opt-in to early access to new functionality before it has shipped by default in Chrome. In this case, we&#39;re allowing developers to opt-in to using legacy technology even after it&#39;s been removed from Chrome, but only temporarily. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;To participate in the &amp;quot;reverse&amp;quot; origin trial:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;a href=&quot;https://developers.chrome.com/origintrials/#/register_trial/1776670052997660673&quot;&gt;Request a token&lt;/a&gt; for your origin.
&lt;/li&gt;
&lt;li&gt;
Add the token to your HTML pages. There are &lt;a href=&quot;https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md#how-do-i-enable-an-experimental-feature-on-my-origin&quot;&gt;two ways&lt;/a&gt; to do that:
&lt;ul&gt;
&lt;li&gt;
Add an &lt;code&gt;origin-trial&lt;/code&gt; &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag to the head of each page. For example: &lt;code&gt;&amp;lt;meta http-equiv=&quot;origin-trial&quot; content=&quot;TOKEN_GOES_HERE&quot;&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
Alternatively, configure your server to return responses containing the &lt;code&gt;Origin-Trial&lt;/code&gt; HTTP header. The resulting response header should look something like: &lt;code&gt;Origin-Trial: TOKEN_GOES_HERE&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Add the same token to your AppCache manifests. Do this via a new field in your manifest, with the format:
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ORIGIN-TRIAL:&lt;br /&gt;TOKEN_GOES_HERE&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;(There needs to be a new line between &lt;code&gt;ORIGIN-TRIAL&lt;/code&gt; and your token.)&lt;/p&gt;
&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 token for a manifest &lt;strong&gt;must&lt;/strong&gt; be in an &lt;code&gt;ORIGIN-TRIAL&lt;/code&gt; field of the manifest itself. Unlike an HTML page&#39;s token, it can&#39;t be provided via an HTTP header. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;You can see a sample project embedded below that demonstrates adding the correct origin trial tokens into both the &lt;code&gt;index.html&lt;/code&gt; and &lt;code&gt;manifest.appcache&lt;/code&gt; files.&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/appcache-reverse-ot?attributionHidden=true&amp;sidebarCollapsed=true&amp;path=manfiest.appcache&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;appcache-reverse-ot on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3 id=&quot;why-are-tokens-needed-in-multiple-places&quot;&gt;Why are tokens needed in multiple places? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#why-are-tokens-needed-in-multiple-places&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;same origin trial token&lt;/strong&gt; needs to be associated with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All of your HTML pages&lt;/strong&gt; that use AppCache.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All of your AppCache manifests&lt;/strong&gt; via the &lt;code&gt;ORIGIN-TRIAL&lt;/code&gt; manifest field.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&#39;ve participated in origin trials in the past, you might have added the token just to your HTML pages. The AppCache &amp;quot;reverse&amp;quot; origin trial is special in that you need to associate a token with each of your AppCache manifests as well.&lt;/p&gt;
&lt;p&gt;Adding the origin trial token to your HTML pages enables the &lt;code&gt;window.applicationCache&lt;/code&gt; interface from within your web apps. Pages that are not associated with a token won&#39;t be able to use &lt;code&gt;window.applicationCache&lt;/code&gt; methods and events. Pages without a token also won&#39;t be able to load resources from the AppCache. Starting with Chrome 85, they will behave as if AppCache did not exist.&lt;/p&gt;
&lt;p&gt;Adding the origin trial token to your AppCache manifests indicates that each manifest is still valid. Starting with Chrome 85, any manifests that does not have an &lt;code&gt;ORIGIN-TRIAL&lt;/code&gt; field will be treated as malformed, and the rules within the manifest will be ignored.&lt;/p&gt;
&lt;h3 id=&quot;origin-trial-deployment-timing-and-logistics&quot;&gt;Origin trial deployment timing and logistics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#origin-trial-deployment-timing-and-logistics&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While the &amp;quot;reverse&amp;quot; origin trial officially starts with Chrome 84, you can &lt;a href=&quot;https://developers.chrome.com/origintrials/#/register_trial/1776670052997660673&quot; rel=&quot;noopener&quot;&gt;sign up&lt;/a&gt; for the origin trial today and add the tokens to your HTML and AppCache manifests. As your web app&#39;s audience gradually upgrades to Chrome 84, any tokens that you&#39;ve already added will go into effect.&lt;/p&gt;
&lt;p&gt;Once you&#39;ve added a token to your AppCache manifest, visit &lt;code&gt;about://appcache-internals&lt;/code&gt; to confirm that your local instance of Chrome (version 84 or later) has properly associated the origin trial token with your manifest&#39;s cached entries. If your origin trial is recognized, you should see a field with &lt;code&gt;Token Expires: Tue Apr 06 2021...&lt;/code&gt; on that page, associated with your manifest:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;about://appcache-internals interface showing a recognized token.&quot; decoding=&quot;async&quot; height=&quot;203&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 550px) 550px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/Xid94kdPT5yGbQzBL4at.jpg?auto=format&amp;w=1100 1100w&quot; width=&quot;550&quot; /&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;testing-prior-to-removal&quot;&gt;Testing prior to removal &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#testing-prior-to-removal&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We strongly encourage you to migrate off of AppCache as soon as is feasible. If you want to test removal of AppCache on your web apps, use the &lt;code&gt;about://flags/#app-cache&lt;/code&gt; &lt;a href=&quot;https://www.chromium.org/developers/how-tos/run-chromium-with-flags&quot; rel=&quot;noopener&quot;&gt;flag&lt;/a&gt; to simulate its removal. This flag is available starting with Chrome 84.&lt;/p&gt;
&lt;h2 id=&quot;migration-strategies&quot;&gt;Migration strategies &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#migration-strategies&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Service workers, which are &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ServiceWorker#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;widely supported in current browsers&lt;/a&gt;, offer an alternative to the offline experience provided by AppCache.&lt;/p&gt;
&lt;p&gt;We&#39;ve provided a &lt;a href=&quot;https://github.com/GoogleChromeLabs/sw-appcache-behavior&quot; rel=&quot;noopener&quot;&gt;polyfill&lt;/a&gt; that uses a service worker to replicate some of the functionality of AppCache, though it does not replicate the entire AppCache interface. In particular, it does not provide a replacement for the &lt;code&gt;window.applicationCache&lt;/code&gt; interface or the related AppCache events.&lt;/p&gt;
&lt;p&gt;For more complex cases, libraries like &lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt; provide an easy way to create a modern service worker for your web app.&lt;/p&gt;
&lt;h3 id=&quot;service-workers-and-appcache-are-mutually-exclusive&quot;&gt;Service workers and AppCache are mutually exclusive &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#service-workers-and-appcache-are-mutually-exclusive&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While working on your migration strategy, please keep in mind that Chrome will disable AppCache functionality on any page that&#39;s loaded under the &lt;a href=&quot;https://web.dev/service-worker-lifecycle/#scope-and-control&quot;&gt;control&lt;/a&gt; of a service worker. In other words, as soon as you deploy a service worker that controls a given page, you&#39;re no longer able to use AppCache on that page.&lt;/p&gt;
&lt;p&gt;Because of this, we recommend that you do not attempt to migrate to service workers piece-by-piece. It would be a mistake to deploy a service worker that only contains some of your caching logic. You cannot fall back on AppCache to &amp;quot;fill in the gaps.&amp;quot;&lt;/p&gt;
&lt;p&gt;Similarly, if you deploy a service worker prior to AppCache removal, and then discover that you need to roll back to your previous AppCache implementation, you need to ensure that you &lt;a href=&quot;https://stackoverflow.com/a/33705250/385997&quot; rel=&quot;noopener&quot;&gt;unregister&lt;/a&gt; that service worker. As long as there&#39;s a registered service worker in scope for a given page, AppCache will not be used.&lt;/p&gt;
&lt;h2 id=&quot;the-cross-platform-story&quot;&gt;The cross-platform story &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#the-cross-platform-story&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We encourage you to follow up with a specific browser vendor if you&#39;d like more information about their plans for AppCache removal.&lt;/p&gt;
&lt;h3 id=&quot;firefox-on-all-platforms&quot;&gt;Firefox on all platforms &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#firefox-on-all-platforms&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Firefox &lt;a href=&quot;https://www.fxsitecompat.dev/en-CA/docs/2015/application-cache-api-has-been-deprecated/&quot; rel=&quot;noopener&quot;&gt;deprecated&lt;/a&gt; AppCache in release 44 (September 2015), and has &lt;a href=&quot;https://www.fxsitecompat.dev/en-CA/docs/2019/application-cache-storage-has-been-removed-in-nightly-and-early-beta/&quot; rel=&quot;noopener&quot;&gt;removed&lt;/a&gt; support for it in its Beta and Nightly builds as of September 2019.&lt;/p&gt;
&lt;h3 id=&quot;safari-on-ios-and-macos&quot;&gt;Safari on iOS and macOS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#safari-on-ios-and-macos&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Safari &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=181764&quot; rel=&quot;noopener&quot;&gt;deprecated&lt;/a&gt; AppCache in early 2018.&lt;/p&gt;
&lt;h3 id=&quot;chrome-on-ios&quot;&gt;Chrome on iOS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#chrome-on-ios&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Chrome for iOS is a special case, as it uses a different browser engine than Chrome on other platforms: the &lt;a href=&quot;https://developer.apple.com/documentation/webkit/wkwebview&quot; rel=&quot;noopener&quot;&gt;WKWebView&lt;/a&gt;. Service workers are not currently supported in iOS apps using WKWebView, and Chrome&#39;s AppCache removal announcement does not cover the &lt;a href=&quot;https://webkit.org/status/#specification-application-cache&quot; rel=&quot;noopener&quot;&gt;availability of AppCache on Chrome for iOS&lt;/a&gt;. Please keep this in mind if you know that your web app has a significant Chrome for iOS audience.&lt;/p&gt;
&lt;h3 id=&quot;android-webviews&quot;&gt;Android WebViews &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#android-webviews&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some developers of Android applications use Chrome &lt;a href=&quot;https://developer.android.com/reference/android/webkit/WebView&quot; rel=&quot;noopener&quot;&gt;WebView&lt;/a&gt; to display web content, and might also use AppCache. However, it&#39;s not possible to enable an origin trial for a WebView. In light of that, Chrome WebView will support AppCache without an origin trial until the final removal takes place, expected in Chrome 90.&lt;/p&gt;
&lt;h2 id=&quot;learn-more&quot;&gt;Learn more &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#learn-more&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are some resources for developers migrating from AppCache to service workers.&lt;/p&gt;
&lt;h3 id=&quot;articles&quot;&gt;Articles &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#articles&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/workbox/service-worker-overview/&quot; rel=&quot;noopener&quot;&gt;Service Workers: an Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/service-worker-lifecycle/&quot;&gt;The Service Worker Lifecycle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/learn/pwa/&quot;&gt;Progressive Web Apps Training&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/reliable/&quot;&gt;Network Reliability&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;tools&quot;&gt;Tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#tools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleChromeLabs/sw-appcache-behavior&quot; rel=&quot;noopener&quot;&gt;AppCache Polyfill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pwabuilder.com/&quot; rel=&quot;noopener&quot;&gt;PWA Builder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getting-help&quot;&gt;Getting help &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/appcache-removal/#getting-help&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you run into an issue using a specific tool, open an issue in its GitHub repository.&lt;/p&gt;
&lt;p&gt;You can ask a general question about migrating off of AppCache on &lt;a href=&quot;https://stackoverflow.com/&quot; rel=&quot;noopener&quot;&gt;Stack Overflow&lt;/a&gt;, using the tag &lt;code&gt;&lt;a href=&quot;https://stackoverflow.com/questions/tagged/html5-appcache&quot; rel=&quot;noopener&quot;&gt;html5-appcache&lt;/a&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you encounter a bug related to Chrome&#39;s AppCache removal, please &lt;a href=&quot;https://crbug.com/new&quot; rel=&quot;noopener&quot;&gt;report it&lt;/a&gt; using the Chromium issue tracker.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Hero image based on &lt;a href=&quot;https://www.si.edu/object/usnm-storage-drawer:siris_arc_391797&quot; rel=&quot;noopener&quot;&gt;Smithsonian Institution Archives, Acc. 11-007, Box 020, Image No. MNH-4477&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Integrate PWAs into built-in sharing UIs with Workbox</title>
    <link href="https://web.dev/workbox-share-targets/"/>
    <updated>2019-12-19T00:00:00Z</updated>
    <id>https://web.dev/workbox-share-targets/</id>
    <content type="html" mode="escaped">&lt;p&gt;The &lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Web Share Target API&lt;/a&gt; lets you display
your &lt;a href=&quot;https://web.dev/pwa-checklist/&quot;&gt;Progressive Web App&lt;/a&gt; in a
user&#39;s system-level share &lt;a href=&quot;https://material.io/develop/android/components/bottom-sheet-behavior/&quot; rel=&quot;noopener&quot;&gt;sheet&lt;/a&gt; after it&#39;s been installed. While it works great if you have a server
available to receive the request, it&#39;s much harder to get working if you don&#39;t.&lt;/p&gt;
&lt;p&gt;In this article we&#39;ll use
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt;, a set of JavaScript
libraries for adding offline support to web apps, to create a share target URL
that lives entirely inside your &lt;a href=&quot;https://web.dev/service-workers-cache-storage/&quot;&gt;service worker&lt;/a&gt;. This lets static sites and
single-page apps serve as share targets without a dedicated server endpoint.&lt;/p&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;Android phone with the &amp;#x27;Share via&amp;#x27; drawer open.&quot; decoding=&quot;async&quot; height=&quot;377&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/mp2bdiP2gVeMQ4UX12vd.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;
    System-level share target picker with an installed PWA called
    &lt;code&gt;Share Target Test&lt;/code&gt; as an option.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;on-the-same-page&quot;&gt;On the same page &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox-share-targets/#on-the-same-page&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;re unfamiliar with how Web Share Target Works, &lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Receiving shared data with the Web Share
Target API&lt;/a&gt; gives you an in-depth introduction.
Here&#39;s a quick review.&lt;/p&gt;
&lt;p&gt;There are two parts to implementing web share target functionality. First,
update your &lt;a href=&quot;https://web.dev/add-manifest/&quot;&gt;web app manifest&lt;/a&gt; to indicate that you want your app to be a share
target when installed. The following example directs shares to the &lt;code&gt;/share&lt;/code&gt; url
via a &lt;code&gt;POST&lt;/code&gt; request. It is encoded as a multipart form, with title being called
&lt;code&gt;name&lt;/code&gt;, text being called &lt;code&gt;description&lt;/code&gt;, and JPEG images being called &lt;code&gt;photos&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;…&lt;br /&gt;&lt;span class=&quot;token property&quot;&gt;&quot;share_target&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;action&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/share&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;method&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&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;enctype&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;multipart/form-data&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;params&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;title&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;name&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;text&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;description&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;files&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;photos&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;accept&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;span class=&quot;token string&quot;&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.jpg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;…&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;service-worker-share-targets-with-workbox&quot;&gt;Service worker share targets with Workbox &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox-share-targets/#service-worker-share-targets-with-workbox&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While normally handled by a server endpoint, a neat trick you can do for a share
target is to register a route directly in your service worker to handle the
request. This will let your app be a share target without a backend.&lt;/p&gt;
&lt;p&gt;You do this in &lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt; by
registering a route that&#39;s handled by your service worker. Start by importing
&lt;code&gt;registerRoute&lt;/code&gt; from &lt;code&gt;&#39;workbox-routing&#39;&lt;/code&gt;. Notice that it&#39;s registered for the
&lt;code&gt;/share&lt;/code&gt; route, the same one listed in the example web app manifest. In
response it calls &lt;code&gt;shareTargetHandler()&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; registerRoute &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;workbox-routing&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;/share&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  shareTargetHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;POST&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;shareTargetHandler()&lt;/code&gt; function is asynchronous and takes the event, awaits
the form data, then retrieves the media files from that.&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;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shareTargetHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; formData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;formData&lt;/span&gt;&lt;span class=&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; mediaFiles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; formData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;media&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token 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; mediaFile &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; mediaFiles&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Do something with mediaFile&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Maybe cache it or post it back to a server&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Do something with the rest of formData as you need&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Maybe save it to IndexedDB&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can then do whatever you&#39;d like with these files. You can cache them. You
can send them somewhere with a fetch request. You can even use the other
manifest options, maybe serving a page with some query parameters for the other
shared items or storing the data and pointers to the media in the &lt;a href=&quot;https://web.dev/cache-api-quick-guide/&quot;&gt;Cache Storage
API&lt;/a&gt;
or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IndexedDB_API&quot; rel=&quot;noopener&quot;&gt;IndexedDB&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can try it out on the sample app &lt;a href=&quot;https://fugu-journal.web.app/&quot; rel=&quot;noopener&quot;&gt;Fugu
Journal&lt;/a&gt; and see its service worker
implementation in its &lt;a href=&quot;https://github.com/chromeos/bridging-the-native-app-gap/blob/master/fugu-journal/src/js/service-worker.js&quot; rel=&quot;noopener&quot;&gt;source
code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One common thing you might do is hold shared resources until better network
connections are available. Workbox also supports &lt;a href=&quot;https://web.dev/periodic-background-sync/&quot;&gt;periodic background
sync&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox-share-targets/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Share Target API is a simple way to deeply integrate your Progressive Web
App into user&#39;s devices, putting them on-par with platform-specific applications for the
critical task of sharing content between apps. But doing so usually requires a
server available to receive the request. By leveraging Workbox to create a share
target route directly in your service worker, your app is free of this
constraint, allowing Share Target to work for apps while offline and without
backends.&lt;/p&gt;
&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@ecasap?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; rel=&quot;noopener&quot;&gt;Elaine Casap&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/share?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content>
    <author>
      <name>Sam Richard</name>
    </author><author>
      <name>Joe Medley</name>
    </author><author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Keeping things fresh with stale-while-revalidate</title>
    <link href="https://web.dev/stale-while-revalidate/"/>
    <updated>2019-07-18T00:00:00Z</updated>
    <id>https://web.dev/stale-while-revalidate/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;what-shipped&quot;&gt;What shipped? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/stale-while-revalidate/#what-shipped&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc5861#section-3&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;stale-while-revalidate&lt;/code&gt;&lt;/a&gt; helps
developers balance between immediacy—&lt;em&gt;loading cached content right away&lt;/em&gt;—and
freshness—&lt;em&gt;ensuring updates to the cached content are used in the future&lt;/em&gt;. If
you maintain a third-party web service or library that updates on a regular
schedule, or your first-party assets tend to have short lifetimes, then
&lt;code&gt;stale-while-revalidate&lt;/code&gt; may be a useful addition to your existing caching
policies.&lt;/p&gt;
&lt;p&gt;Support for setting &lt;code&gt;stale-while-revalidate&lt;/code&gt; alongside &lt;code&gt;max-age&lt;/code&gt; in your
&lt;code&gt;Cache-Control&lt;/code&gt; response header is available in &lt;a href=&quot;https://chromestatus.com/feature/5050913014153216&quot; rel=&quot;noopener&quot;&gt;Chrome 75&lt;/a&gt;
and &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1536511&quot; rel=&quot;noopener&quot;&gt;Firefox 68&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Browsers that don&#39;t support &lt;code&gt;stale-while-revalidate&lt;/code&gt; will silently ignore that
configuration value, and use
&lt;a href=&quot;https://web.dev/http-cache/#versioned-urls&quot;&gt;&lt;code&gt;max-age&lt;/code&gt;&lt;/a&gt;,
as I&#39;ll explain shortly…&lt;/p&gt;
&lt;h2 id=&quot;whats-it-mean&quot;&gt;What&#39;s it mean? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/stale-while-revalidate/#whats-it-mean&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&#39;s break down &lt;code&gt;stale-while-revalidate&lt;/code&gt; into two parts: the idea that a cached
response might be stale, and the process of revalidation.&lt;/p&gt;
&lt;p&gt;First, how does the browser know whether a cached response is &amp;quot;stale&amp;quot;? A
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Cache-Control&lt;/code&gt;&lt;/a&gt;
response header that contains &lt;code&gt;stale-while-revalidate&lt;/code&gt; should also contain
&lt;code&gt;max-age&lt;/code&gt;, and the number of seconds specified via &lt;code&gt;max-age&lt;/code&gt; is what determines
staleness. Any cached response newer than &lt;code&gt;max-age&lt;/code&gt; is considered fresh, and
older cached responses are stale.&lt;/p&gt;
&lt;p&gt;If the locally cached response is still fresh, then it can be used as-is to
fulfill a browser&#39;s request. From the perspective of &lt;code&gt;stale-while-revalidate&lt;/code&gt;,
there&#39;s nothing to do in this scenario.&lt;/p&gt;
&lt;p&gt;But if the cached response is stale, then another age-based check is performed:
is the age of the cached response within the additional time window provided by the
&lt;code&gt;stale-while-revalidate&lt;/code&gt; setting?&lt;/p&gt;
&lt;p&gt;If the age of a stale response falls into this window, then it will be used to
fulfill the browser&#39;s request. At the same time, a &amp;quot;revalidation&amp;quot; request will
be made against the network in a way that doesn&#39;t delay the use of the cached
response. The returned response might contain the same information as the
previously cached response, or it might be different. Either way, the network
response is stored locally, replacing whatever was previously cache, and
resetting the &amp;quot;freshness&amp;quot; timer used during any future &lt;code&gt;max-age&lt;/code&gt; comparisons.&lt;/p&gt;
&lt;p&gt;However, if the stale cached response is old enough that it falls outside the
&lt;code&gt;stale-while-revalidate&lt;/code&gt; window of time, then it will not fulfill the browser&#39;s
request. The browser will instead retrieve a response from the network, and use
that for both fulfilling the initial request and also populating the local cache
with a fresh response.&lt;/p&gt;
&lt;h2 id=&quot;live-example&quot;&gt;Live Example &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/stale-while-revalidate/#live-example&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Below is a simple example of an HTTP API for returning the current time—more
specifically, the current number of minutes past the hour.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 346px; 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/s-w-r-demo?attributionHidden=true&amp;sidebarCollapsed=true&amp;path=server.js%3A20%3A15&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;s-w-r-demo on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;In this scenario, the web server uses this &lt;code&gt;Cache-Control&lt;/code&gt; header in its HTTP response:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Cache-Control: max-age=1, stale-while-revalidate=59&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This setting means that, if a request for the time is repeated within the next 1
second, the previously cached value will still be fresh, and used as-is, without
any revalidation.&lt;/p&gt;
&lt;p&gt;If a request is repeated between 1 and 60 seconds later, then the cached value
will be stale, but will be used to fulfill the API request. At the same time,
&amp;quot;in the background,&amp;quot; a revalidation request will be made to populate the cache
with a fresh value for future use.&lt;/p&gt;
&lt;p&gt;If a request is repeated after more than 60 seconds, then the stale response
isn&#39;t used at all, and both fulfilling the browser&#39;s request and the cache
revalidation will depend on getting a response back from the network.&lt;/p&gt;
&lt;p&gt;Here&#39;s a breakdown of those three distinct states, along with the window of time
in which each of them apply for our example:&lt;/p&gt;
&lt;img alt=&quot;A diagram illustrating the information from the previous section.&quot; decoding=&quot;async&quot; height=&quot;370&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/admin/C8lg2FSEqhTKR6WmYky3.svg&quot; width=&quot;719&quot; /&gt;
&lt;h2 id=&quot;what-are-the-common-use-cases&quot;&gt;What are the common use cases? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/stale-while-revalidate/#what-are-the-common-use-cases&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While the above example for a &amp;quot;minutes after the hour&amp;quot; API service is contrived,
it illustrates the expected use case—services that provide information which
needs to be refreshed, but where some degree of staleness is acceptable.&lt;/p&gt;
&lt;p&gt;Less contrived examples might be an API for the current weather conditions, or
the top news headlines that were written in the past hour.&lt;/p&gt;
&lt;p&gt;Generally, any response that updates at a known interval, is likely to be
requested multiple times, and is static within that interval is a good candidate
for short-term caching via &lt;code&gt;max-age&lt;/code&gt;. Using &lt;code&gt;stale-while-revalidate&lt;/code&gt; in addition
to &lt;code&gt;max-age&lt;/code&gt; increases the likelihood that future requests can be fulfilled from
the cache with fresher content, without blocking on a network response.&lt;/p&gt;
&lt;h2 id=&quot;how-does-it-interact-with-service-workers&quot;&gt;How does it interact with service workers? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/stale-while-revalidate/#how-does-it-interact-with-service-workers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;ve heard of &lt;code&gt;stale-while-revalidate&lt;/code&gt; chances are that it was in the
context of
&lt;a href=&quot;https://web.dev/stale-while-revalidate/offline-cookbook/#stale-while-revalidate&quot;&gt;recipes&lt;/a&gt;
used within a &lt;a href=&quot;https://web.dev/service-workers-cache-storage/&quot;&gt;service worker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using stale-while-revalidate via a &lt;code&gt;Cache-Control&lt;/code&gt; header shares some
similarities with its use in a service worker, and many of the same
considerations around freshness trade-offs and maximum lifetimes apply. However,
there are a few considerations that you should take into account when deciding
whether to implement a service worker-based approach, or just rely on the
&lt;code&gt;Cache-Control&lt;/code&gt; header configuration.&lt;/p&gt;
&lt;h3 id=&quot;use-a-service-worker-approach-if&quot;&gt;Use a service worker approach if… &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/stale-while-revalidate/#use-a-service-worker-approach-if&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You&#39;re already using a service worker in your web app.&lt;/li&gt;
&lt;li&gt;You need fine-grained control over the contents of your caches, and want to
implement something like a least-recently used expiration policy. Workbox&#39;s
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-expiration/&quot; rel=&quot;noopener&quot;&gt;Cache Expiration&lt;/a&gt;
module can help with this.&lt;/li&gt;
&lt;li&gt;You want to be notified when a stale response changes in the background during
the revalidation step. Workbox&#39;s
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-broadcast-update/&quot; rel=&quot;noopener&quot;&gt;Broadcast Cache Update&lt;/a&gt;
module can help with this.&lt;/li&gt;
&lt;li&gt;You need this &lt;code&gt;stale-while-revalidate&lt;/code&gt; behavior in all modern browsers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;use-a-cache-control-approach-if&quot;&gt;Use a Cache-Control approach if… &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/stale-while-revalidate/#use-a-cache-control-approach-if&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You would rather not deal with the overhead of deploying and maintaining a
service worker for your web app.&lt;/li&gt;
&lt;li&gt;You are fine with letting the browser&#39;s automatic cache management prevent
your local caches from growing too large.&lt;/li&gt;
&lt;li&gt;You are fine with an approach that is not currently supported in all modern
browsers (as of July 2019; support may grow in the future).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&#39;re using a service worker and also have &lt;code&gt;stale-while-revalidate&lt;/code&gt; enabled
for some responses via a &lt;code&gt;Cache-Control&lt;/code&gt; header, then the service worker will,
in general, have &amp;quot;first crack&amp;quot; at responding to a request. If the service worker
decides not to respond, or if in the process of generating a response it makes a
network request using &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Fetch_API&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;fetch()&lt;/code&gt;&lt;/a&gt;,
then the behavior configured via the &lt;code&gt;Cache-Control&lt;/code&gt; header will end up going
into effect.&lt;/p&gt;
&lt;h2 id=&quot;learn-more&quot;&gt;Learn more &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/stale-while-revalidate/#learn-more&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fetch.spec.whatwg.org/#concept-stale-while-revalidate-response&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;stale-while-revalidate&lt;/code&gt; response&lt;/a&gt;
in the Fetch API spec.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc5861&quot; rel=&quot;noopener&quot;&gt;RFC 5861&lt;/a&gt;, covering the initial
&lt;code&gt;stale-while-revalidate&lt;/code&gt; specification.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/http-cache/&quot;&gt;The HTTP cache: your first line of defense&lt;/a&gt;, from the &amp;quot;Network
reliability&amp;quot; guide on this site.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Hero image by Samuel Zeller.&lt;/em&gt;&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Bringing service workers to Google Search</title>
    <link href="https://web.dev/google-search-sw/"/>
    <updated>2019-06-20T00:00:00Z</updated>
    <id>https://web.dev/google-search-sw/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;background&quot;&gt;Background &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#background&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Search for just about any topic on Google, and you&#39;re presented with an
instantly recognizable page of meaningful, relevant results. What you probably
&lt;em&gt;didn&#39;t&lt;/em&gt; realize is that this search results page is, under certain scenarios,
served by a powerful piece of web technology called a
&lt;a href=&quot;https://web.dev/service-workers-cache-storage/&quot;&gt;service worker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Rolling out service worker support for Google Search without negatively
impacting the performance required dozens of engineers working across multiple
teams. This is the story of what shipped, how performance was measured, and what
tradeoffs were made.&lt;/p&gt;
&lt;h2 id=&quot;key-reasons-for-exploring-service-workers&quot;&gt;Key reasons for exploring service workers &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#key-reasons-for-exploring-service-workers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Adding a service worker to a web app, just like making any architectural change
to your site, should be done with a clear set of goals in mind. For the Google
Search team, there were a few key reasons why adding a service worker was worth
exploring.&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; A service worker is extra code that sits in between your web app and the network, and running that code isn&#39;t free, so you need to make sure that what you&#39;re doing inside the service worker adds enough of a caching or functionality benefit to justify the cost of running the code. (This &lt;a href=&quot;https://www.youtube.com/watch?v=25aCD5XL1Jk&quot;&gt;talk&lt;/a&gt; at the Chrome Dev Summit 2018 does a great job of exploring that idea in more detail.) An upfront understanding what you hope to achieve—and then collecting a full set of metrics to ensure that you&#39;ve actually achieved it—should be the first step in your service worker journey. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;limited-search-result-caching&quot;&gt;Limited search result caching &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#limited-search-result-caching&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Google Search team found that it&#39;s common for users to search for the
same terms more than once within a short period of time. Rather than trigger a
new backend request just to get what&#39;s likely to be the same results, the Search
team wanted to take advantage of caching and fulfill those repeat requests
locally.&lt;/p&gt;
&lt;p&gt;The importance of freshness can&#39;t be discounted, and sometimes users search for
the same terms repeatedly because it&#39;s an evolving topic, and they expect to see
fresh results. Using a service worker allows the Search team to implement
fine-grained logic to control the lifetime of locally cached search results, and
achieve the exact balance of speed vs. freshness that they believe best serves
users.&lt;/p&gt;
&lt;h3 id=&quot;meaningful-offline-experience&quot;&gt;Meaningful offline experience &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#meaningful-offline-experience&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Additionally, the Google Search team wanted to provide a meaningful offline
experience. When a user wants to find out about a topic, they want to go
straight to the Google Search page and start searching, without worrying about
an active Internet connection.&lt;/p&gt;
&lt;p&gt;Without a service worker, visiting the Google search page while offline would
just lead to the browser&#39;s standard network error page, and users would have to
remember to come back and try again once their connection returned. With a
service worker, it&#39;s possible to serve a custom offline HTML response, and allow
users to enter their search query immediately.&lt;/p&gt;
&lt;img alt=&quot;A screenshot of the background retry interface.&quot; decoding=&quot;async&quot; height=&quot;634&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 343px) 343px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/Q60UfWp6FNp0b9vbqJXc.png?auto=format&amp;w=686 686w&quot; width=&quot;343&quot; /&gt;
&lt;p&gt;The results won&#39;t be available until there&#39;s an Internet connection, but the
service worker allows the search to be deferred and sent to Google&#39;s servers as
soon as the device goes back online using the
&lt;a href=&quot;https://developer.chrome.com/blog/background-sync/&quot; rel=&quot;noopener&quot;&gt;background sync API&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;smarter-javascript-caching-and-serving&quot;&gt;Smarter JavaScript caching and serving &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#smarter-javascript-caching-and-serving&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another motivation was to optimize the caching and loading of the modularized
JavaScript code that powers the various types of features on the search results
page. There are a number of benefits offered by JavaScript bundling that make
sense when there&#39;s no service worker involvement, so the Search team did not
want to simply stop bundling entirely.&lt;/p&gt;
&lt;p&gt;By using a service worker&#39;s ability to version and cache fine-grained chunks of
JavaScript at runtime, the Search team suspected that they could reduce the
amount of cache churn and ensure that JavaScript reused in the
future can be cached efficiently. The logic inside of their service worker can
analyze an outgoing HTTP request for a bundle that contains multiple JavaScript
modules, and fulfill it by piecing together multiple, locally cached
modules—effectively &amp;quot;unbundling&amp;quot; when possible. This saves user bandwidth, and
improves overall responsiveness.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; On average, repeat visits handled by the service worker result in &lt;strong&gt;half as much new JavaScript downloaded&lt;/strong&gt;, and that directly leads to &lt;strong&gt;6% fewer delayed user interactions&lt;/strong&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;There are also performance benefits of using cached JavaScript served by a
service worker: in Chrome, &lt;a href=&quot;https://v8.dev/blog/code-caching-for-devs#use-service-worker-caches&quot; rel=&quot;noopener&quot;&gt;a parsed, byte code representation&lt;/a&gt;
of that JavaScript is stored and reused, leading to less work that needs to be
done at runtime in order to execute the JavaScript on the page.&lt;/p&gt;
&lt;h2 id=&quot;challenges-and-solutions&quot;&gt;Challenges and solutions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#challenges-and-solutions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are a few of the hurdles that needed to be overcome in order to achieve the
team&#39;s stated goals. While some of these challenges are specific to Google
Search, many of them are applicable to a wide range of sites that might be
considering a service worker deployment.&lt;/p&gt;
&lt;h3 id=&quot;problem-service-worker-overhead&quot;&gt;Problem: service worker overhead &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#problem-service-worker-overhead&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The biggest challenge, and the one true blocker for launching a service worker
on Google Search, was to ensure that it did not do anything that might increase
user-perceived latency. Google Search takes performance &lt;em&gt;very&lt;/em&gt; seriously, and in
the past, has blocked launches of new functionality if it contributed even tens
of milliseconds of additional latency for a given user population.&lt;/p&gt;
&lt;p&gt;When the team started collecting performance data during their earliest
experiments, it became obvious that there would be a problem. The HTML returned
in response to
&lt;a href=&quot;https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests&quot; rel=&quot;noopener&quot;&gt;navigation requests&lt;/a&gt;
for the search result page is dynamic, and varies greatly depending on logic
that needs to run on Search&#39;s web servers. There&#39;s currently no way for the
service worker to replicate this logic and return cached HTML immediately—the
best it could do is to pass along navigation requests to the backend web
servers, which necessitates a network request.&lt;/p&gt;
&lt;p&gt;Without a service worker, this network request happens immediately upon user
navigation. When a service worker is registered, it always needs to be started
up and given a chance to execute its
&lt;a href=&quot;https://developers.google.com/web/fundamentals/primers/service-workers/#cache_and_return_requests&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;fetch&lt;/code&gt; event handlers&lt;/a&gt;,
even when there&#39;s no chance those fetch handlers will do anything other than go
to the network. The amount of time that it takes to start up and run the service
worker code is pure overhead added on top of every navigation:&lt;/p&gt;
&lt;img alt=&quot;An illustration of the SW startup blocking the navigation request.&quot; decoding=&quot;async&quot; height=&quot;96&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 768px) 768px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/W9v9DmCDOo6VJp7gOXPB.png?auto=format&amp;w=1536 1536w&quot; width=&quot;768&quot; /&gt;
&lt;p&gt;This puts the service worker implementation at too much of a latency
disadvantage to justify any other benefits. Additionally, the team found that,
based on measuring service worker boot times on real-world devices, there was a
wide distribution of startup times, with some low-end mobile devices taking
almost as much time to start up the service worker as it might take to make the
network request for the results page&#39;s HTML.&lt;/p&gt;
&lt;h3 id=&quot;solution-use-navigation-preload&quot;&gt;Solution: use navigation preload &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#solution-use-navigation-preload&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The single, most crucial feature that allowed the Google Search team to move
ahead with their service worker launch is
&lt;a href=&quot;https://developer.chrome.com/blog/navigation-preload/&quot; rel=&quot;noopener&quot;&gt;navigation preload&lt;/a&gt;.
Using navigation preload is a key performance win for any service worker that
needs to use a response from the network to satisfy navigation requests. It
provides a hint to the browser to start making the navigation request
immediately, at the same time as the service worker starts up:&lt;/p&gt;
&lt;img alt=&quot;An illustration of the SW startup done in parallel with the navigation request.&quot; decoding=&quot;async&quot; height=&quot;179&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 614px) 614px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/rFkXhIK4xLREiROkxITA.png?auto=format&amp;w=1228 1228w&quot; width=&quot;614&quot; /&gt;
&lt;p&gt;As long as the amount of time it takes for the service worker to start up is
less than the amount of time it takes to get a response back from the network,
there shouldn&#39;t be any latency overhead introduced by the service worker.&lt;/p&gt;
&lt;p&gt;The Search team also needed to avoid using a service worker on low-end mobile
devices where the service worker boot time could exceed the navigation request.
Since there&#39;s no hard-and-fast rule for what constitutes a &amp;quot;low-end&amp;quot; device,
they came up with the heuristic of
&lt;a href=&quot;https://developer.chrome.com/blog/device-memory/&quot; rel=&quot;noopener&quot;&gt;checking the total RAM&lt;/a&gt;
installed on the device. Anything less than 2 gigabytes of memory fell into
their low-end device category, where service worker startup time would be unacceptable.&lt;/p&gt;
&lt;p&gt;Available storage space is another consideration, since the full set of
resources to be cached for future use can run to several megabytes. The
&lt;a href=&quot;https://developers.google.com/web/updates/2017/08/estimating-available-storage-space&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;navigator.storage&lt;/code&gt; interface&lt;/a&gt;
allows the Google Search page to figure out in advance whether their attempts to
cache data run the risk of failing due to storage quota failures.&lt;/p&gt;
&lt;p&gt;This left the Search team with multiple pieces of criteria that they could use
to determine whether or not to use a service worker: if a user comes to the
Google Search page using a browser that supports navigation preload, and has at
least 2 gigabytes of RAM, and enough free storage space, then a
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#the-first-service-worker&quot;&gt;service worker is registered&lt;/a&gt;.
Browsers or devices that don&#39;t meet that criteria won&#39;t end up with a service
worker, but they&#39;ll still see the same Google Search experience as they always
have.&lt;/p&gt;
&lt;p&gt;One side benefit of this selective registration is the ability to ship a
smaller, more efficient service worker. Targeting fairly modern browsers to run
the service worker code eliminates the overhead of transpilation and polyfills
for older browsers. This ended up cutting out around 8 kilobytes of uncompressed
JavaScript code from the total size of the service worker&#39;s implementation.&lt;/p&gt;
&lt;h3 id=&quot;problem-service-worker-scopes&quot;&gt;Problem: service worker scopes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#problem-service-worker-scopes&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once the Search team ran enough latency experiments and were confident that
using navigation preload offered them a viable, latency-neutral path for using a
service worker, some practical issues started moving to the forefront. One of
those issues has to do with service worker&#39;s
&lt;a href=&quot;https://developers.google.com/web/ilt/pwa/introduction-to-service-worker#registration_and_scope&quot; rel=&quot;noopener&quot;&gt;scoping rules&lt;/a&gt;.
A service worker&#39;s scope determines which pages it can potentially take control
of.&lt;/p&gt;
&lt;p&gt;Scoping works based on the URL path prefix. For domains that host a single
web app, this isn&#39;t an issue, as you&#39;d normally just use a service worker with
the maximal scope of &lt;code&gt;/&lt;/code&gt;, which could take control of any page under the domain.
But Google Search&#39;s URL structure is a little more complicated.&lt;/p&gt;
&lt;p&gt;If the service worker were given the maximal scope of &lt;code&gt;/&lt;/code&gt;, it would end up being
able to take control of any page hosted under &lt;code&gt;www.google.com&lt;/code&gt; (or the regional
equivalent), and there are URLs under that domain that have nothing to do with
Google Search. A more reasonable, restrictive scope would be &lt;code&gt;/search&lt;/code&gt;, which at
least would eliminate URLs completely unrelated to search results.&lt;/p&gt;
&lt;p&gt;Unfortunately, even that &lt;code&gt;/search&lt;/code&gt; URL path is shared amongst different flavor
of Google Search results, with URL query parameters determining which specific
type of search result is shown. Some of those flavors use completely different
codebases than the traditional web search result page. For example, Image Search
and Shopping Search are both served under the &lt;code&gt;/search&lt;/code&gt; URL path with different
query parameters, but neither of those interfaces were ready to ship their own
service worker experience (yet).&lt;/p&gt;
&lt;h3 id=&quot;solution-create-a-dispatch-and-routing-framework&quot;&gt;Solution: create a dispatch and routing framework &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#solution-create-a-dispatch-and-routing-framework&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While there are &lt;a href=&quot;https://github.com/w3c/ServiceWorker/issues/1373&quot; rel=&quot;noopener&quot;&gt;some proposals&lt;/a&gt;
that allow for something more powerful than URL path prefixes to determine
service worker scopes, the Google Search team was stuck deploying a service
worker that did nothing for a subset of pages it controlled.&lt;/p&gt;
&lt;p&gt;To work around this, the Google Search team built up a bespoke dispatch and
routing framework that could be configured to check for criteria like the query
parameters of the client page, and use those to determine which specific code
path to go down. Rather than hardcoding rules, the system was built to be
flexible and allow teams that share the URL space, like Image Search and
Shopping Search, to drop in their own service worker logic down the line, if
they decide to implement it.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; While this custom solution is internal to Google, the same general principle can be applied to any domain that includes a number of different logical web apps, all of whom live under a common URL. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;problem-personalized-results-and-metrics&quot;&gt;Problem: personalized results and metrics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#problem-personalized-results-and-metrics&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Users can sign in to Google Search using their Google Accounts, and their search
results experience may be customized based on their particular account data.
Logged in users are identified by specific &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Cookies&quot; rel=&quot;noopener&quot;&gt;browser cookies&lt;/a&gt;,
which is a venerable and widely-supported standard.&lt;/p&gt;
&lt;p&gt;One downside of using browser cookies, though, is that they are not exposed
inside of a service worker, and there is no way of automatically examining their
values and ensuring that they have not changed due to a user logging out or
switching accounts. (There is effort underway to
&lt;a href=&quot;https://developers.google.com/web/updates/2018/09/asynchronous-access-to-http-cookies#welcome_service_workers&quot; rel=&quot;noopener&quot;&gt;bring cookie access to service workers&lt;/a&gt;,
but as of this writing, the approach is
&lt;a href=&quot;https://developers.google.com/web/updates/2018/09/asynchronous-access-to-http-cookies#origin-trial&quot; rel=&quot;noopener&quot;&gt;experimental&lt;/a&gt;
and is not widely supported.)&lt;/p&gt;
&lt;p&gt;A mismatch between the service worker&#39;s view of the current logged in user and
the actual user logged in to the Google Search web interface could lead to
incorrectly personalized search results or misattributed metrics and logging.
Any of those failure scenarios would be a serious issue for the Google Search
team.&lt;/p&gt;
&lt;h3 id=&quot;solution-send-cookies-using-postmessage&quot;&gt;Solution: send cookies using postMessage &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#solution-send-cookies-using-postmessage&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Rather than wait for experimental APIs to launch and provide direct access to
the browser&#39;s cookies inside of a service worker, the Google Search team went
with a stop-gap solution: whenever a page controlled by the service worker is
loaded, the page reads the relevant cookies and uses
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Worker/postMessage&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;postMessage()&lt;/code&gt;&lt;/a&gt;
to send them to the service worker.&lt;/p&gt;
&lt;p&gt;The service worker then checks the current cookie value against the value
that it expects, and if there&#39;s a mismatch, it takes steps to purge any
user-specific data from its storage, and reloads the search results page without
any incorrect personalization.&lt;/p&gt;
&lt;p&gt;The specific steps that the service worker takes to reset things to a baseline
are particular to Google Search&#39;s requirements, but the same general approach
may be useful to other developers who deal with personalized data keyed off of
browsers cookies.&lt;/p&gt;
&lt;h3 id=&quot;problem-experiments-and-dynamism&quot;&gt;Problem: experiments and dynamism &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#problem-experiments-and-dynamism&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As mentioned, the Google Search team relies heavily on running experiments in
production, and testing the effects of new code and features in the real world
before turning them on by default. This can be a bit of a challenge with a
static service worker that relies heavily on cached data, since opting users in
and out of experiments often requires communication with the backend server.&lt;/p&gt;
&lt;h3 id=&quot;solution-dynamically-generated-service-worker-script&quot;&gt;Solution: dynamically generated service worker script &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#solution-dynamically-generated-service-worker-script&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The solution that the team went with was to use a dynamically generated service
worker script, customized by the web server for each individual user, instead of
a single, static service worker script that gets generated ahead of time.
Information about experiments that might affect the service worker&#39;s behavior or
network requests in general are included directly in this customized service
worker scripts. Changing the sets of active experiences for a user is done via a
combination of traditional techniques, like browser cookies, as well as serving
updated code in the registered service worker URL.&lt;/p&gt;
&lt;p&gt;Using a dynamically generated service worker script also makes it easier to
provide an escape hatch in the unlikely event that a service worker
implementation has a fatal bug that needs to be avoided. The dynamic server
worker response could be a &lt;a href=&quot;https://stackoverflow.com/a/38980776/385997&quot; rel=&quot;noopener&quot;&gt;no-op implementation&lt;/a&gt;,
effectively disabling the service worker for some or all of the current users.&lt;/p&gt;
&lt;h3 id=&quot;problem-coordinating-updates&quot;&gt;Problem: coordinating updates &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#problem-coordinating-updates&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the toughest challenges facing any real-world service worker deployment
is to devise a reasonable tradeoff between avoiding the network in favor of the
cache, while at the same time, ensuring that existing users get critical updates
and changes soon after they&#39;re deployed to production. The right balance depends
on a lot of factors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Whether your web app is a long-lived &lt;a href=&quot;https://en.wikipedia.org/wiki/Single-page_application&quot; rel=&quot;noopener&quot;&gt;single page app&lt;/a&gt;
that a user keeps open indefinitely, without navigating to new pages.&lt;/li&gt;
&lt;li&gt;What the deployment cadence is for updates to your backend web server.&lt;/li&gt;
&lt;li&gt;Whether the average user would tolerate using a slightly out-of-date version
of your web app, or whether freshness is the top priority.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While experimenting with service workers, the Google Search team made sure to
keep the experiments running across a number of scheduled backend updates, to
ensure that the metrics and user experience would more closely match what return
users would end up seeing in the real-world.&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; It&#39;s important to remember that shipping a service worker is &lt;strong&gt;not a one-time deployment&lt;/strong&gt;—you need to have a process in place, tailored to your own production infrastructure, to make sure that updates happen smoothly over time! &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;solution-balance-freshness-and-cache-utilization&quot;&gt;Solution: balance freshness and cache-utilization &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#solution-balance-freshness-and-cache-utilization&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After testing a number of different configuration options, the Google Search
team found that the following setup provided the right balance between freshness
and cache-utilization.&lt;/p&gt;
&lt;p&gt;The service worker script URL is served with the
&lt;code&gt;Cache-Control: private, max-age=1500&lt;/code&gt; (1500 seconds, or 25 minutes) response
header, and is
&lt;a href=&quot;https://developers.google.com/web/updates/2018/06/fresher-sw#updateviacache&quot; rel=&quot;noopener&quot;&gt;registered with updateViaCache set to &#39;all&#39;&lt;/a&gt;
to ensure that the header is honored. The Google Search web backend is, as you
might imagine, a large, globally distributed set of servers that requires as
close to 100% uptime as possible. Deploying a change that would affect the
service worker script&#39;s contents is done in a rolling fashion.&lt;/p&gt;
&lt;p&gt;If a user hits a backend that has been updated, and then quickly navigates to
another page which hits a backend that hasn&#39;t yet received the updated service
worker, they&#39;d end up flip-flopping between versions multiple times. Therefore,
telling the browser to only bother checking for an updated script if 25 minutes
has passed since the last check does not have a significant downside. The upside
of opting-in to this behavior is cutting down significantly on the traffic
received by the endpoint that dynamically generates the service worker script.&lt;/p&gt;
&lt;p&gt;Additionally, an ETag header is set on the service worker script&#39;s HTTP
response, ensuring that when an update check is made after 25 minutes has
passed, the server can respond efficiently with an &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Status/304&quot; rel=&quot;noopener&quot;&gt;HTTP 304&lt;/a&gt;
response if there haven&#39;t been any updates to the service worker deployed in the
interim.&lt;/p&gt;
&lt;p&gt;While some interactions within the Google Search web app use single page
app-style navigations (i.e. via the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/History_API#Adding_and_modifying_history_entries&quot; rel=&quot;noopener&quot;&gt;History API&lt;/a&gt;),
for the most part, Google Search is a traditional web app that uses &amp;quot;real&amp;quot;
navigations. This comes into play when the team decided that it would be
effective to use two options that accelerate the service worker update
lifecycle:
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#clientsclaim&quot;&gt;&lt;code&gt;clients.claim()&lt;/code&gt;&lt;/a&gt;
and
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#skip-the-waiting-phase&quot;&gt;&lt;code&gt;skipWaiting()&lt;/code&gt;&lt;/a&gt;.
Clicking around Google Search&#39;s interface generally ends up navigating to new
HTML documents. Calling &lt;code&gt;skipWaiting&lt;/code&gt; ensures that an updated service worker
gets a chance to handle those new navigation requests immediately after
installation. Similarly, calling &lt;code&gt;clients.claim()&lt;/code&gt; means that the updated
service worker gets a chance to start controlling any open Google Search pages
that are uncontrolled, following service worker activation.&lt;/p&gt;
&lt;p&gt;The approach that Google Search went with isn&#39;t necessarily a solution that
works for everyone—it was the result of carefully A/B testing various
combinations of serving options until they found what worked best for them.
Developers whose backend infrastructure allow them to deploy updates more
quickly might prefer that the browser check for an updated service worker script
as frequently as possible, by
&lt;a href=&quot;https://developers.google.com/web/updates/2018/06/fresher-sw#whats_changing&quot; rel=&quot;noopener&quot;&gt;always ignoring the HTTP cache&lt;/a&gt;.
If you&#39;re building a single page app that users will might keep open for a long
period of time, using &lt;code&gt;skipWaiting()&lt;/code&gt; is probably not the right choice for
you—you
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#skip-the-waiting-phase&quot;&gt;risk running into cache inconsistencies&lt;/a&gt;
if you allow the new service worker to activate while there are long-lived
clients.&lt;/p&gt;
&lt;h2 id=&quot;key-takeaways&quot;&gt;Key Takeaways &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#key-takeaways&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;by-default,-service-workers-arent-performance-neutral&quot;&gt;By default, service workers aren&#39;t performance neutral &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#by-default,-service-workers-arent-performance-neutral&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Adding a service worker to your web app means inserting an additional piece of
JavaScript that needs to be loaded and executed before your web app gets
responses to its requests. If those responses end up coming from a local cache
rather than from the network, then the overhead of running the service worker
is usually negligible in comparison to the performance win from going
cache-first. But if you know that your service worker always has to
consult the network when handling navigation requests, using navigation preload
is a crucial performance win.&lt;/p&gt;
&lt;h3 id=&quot;service-workers-are-still-a-progressive-enhancement&quot;&gt;Service workers are (still!) a progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#service-workers-are-still-a-progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The service worker support story is much brighter today than it was even a year
ago. All modern browsers now feature at least some
&lt;a href=&quot;https://jakearchibald.github.io/isserviceworkerready/&quot; rel=&quot;noopener&quot;&gt;support for service workers&lt;/a&gt;,
but unfortunately, there are some advanced service worker features—like
background sync and navigation preload—that aren&#39;t rolled out universally.
Feature checking for the specific subset of features that you know you need, and
only registering a service worker when those are present, is still a reasonable
approach to take.&lt;/p&gt;
&lt;p&gt;Similarly, if you&#39;ve run experiments in the wild, and know that low-end devices
end up performing poorly with the additional overhead of a service worker, you
can abstain from registering a service worker in those scenarios as well.&lt;/p&gt;
&lt;p&gt;You should continue to treat service workers as a &lt;a href=&quot;https://en.wikipedia.org/wiki/Progressive_enhancement&quot; rel=&quot;noopener&quot;&gt;progressive enhancement&lt;/a&gt;
that gets added to a web app when all the prerequisites are met and the service
worker adds something positive to user experience and overall loading
performance.&lt;/p&gt;
&lt;h3 id=&quot;measure-everything&quot;&gt;Measure everything &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#measure-everything&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The only way you can figure out whether shipping a service worker has had a
positive or negative impact on your users&#39; experiences is to experiment and
measure the results.&lt;/p&gt;
&lt;p&gt;The specifics of setting up meaningful measurements depends on what
analytics provider you&#39;re using, and how you normally conduct experiments in
your deployment setup. One approach, using Google Analytics to collect metrics,
is detailed in
&lt;a href=&quot;https://developers.google.com/web/showcase/2016/service-worker-perf&quot; rel=&quot;noopener&quot;&gt;this case study&lt;/a&gt;
based on the experience using service workers in the Google I/O web app.&lt;/p&gt;
&lt;h2 id=&quot;non-goals&quot;&gt;Non-goals &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#non-goals&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While many in the web development community associate service workers with &lt;a href=&quot;https://web.dev/progressive-web-apps/&quot;&gt;Progressive Web Apps&lt;/a&gt;,
building a &amp;quot;Google Search PWA&amp;quot; was not an initial goal of the team. The Google
Search web app doesn&#39;t currently provide metadata via a
&lt;a href=&quot;https://web.dev/add-manifest/&quot;&gt;web app manifest&lt;/a&gt;,
nor does it encourage users to go through the
&lt;a href=&quot;https://developers.google.com/web/fundamentals/app-install-banners/&quot; rel=&quot;noopener&quot;&gt;Add to Home Screen flow&lt;/a&gt;.
The Search team is currently satisfied with users coming to their web app via
the traditional entry points for Google Search.&lt;/p&gt;
&lt;p&gt;Rather than trying to turn the Google Search web experience into the equivalent
of what you&#39;d expect from an installed application, the focus on the initial
roll out was to progressively enhance the existing web site.&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/google-search-sw/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thanks to the entire Google Search web development team for their work on the
service worker implementation, and for sharing the background material that went
into writing this article. Particular thanks goes to Philippe Golle, Rajesh
Jagannathan, R. Samuel Klatchko, Andy Martone, Leonardo Peña, Rachel Shearer,
Greg Terrono, and Clay Woolam.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update (Oct. 2021): Since this article was originally published, the Google Search team has reevaluated the benefits and tradeoffs of their current service worker architecture. The service worker described above is being retired. As the Google Search web infrastructure evolves, the team may revisit their service worker design.&lt;/em&gt;&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Code splitting with React.lazy and Suspense</title>
    <link href="https://web.dev/code-splitting-suspense/"/>
    <updated>2019-04-29T00:00:00Z</updated>
    <id>https://web.dev/code-splitting-suspense/</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; If you don&#39;t yet understand the basic idea behind code splitting, refer to &lt;a href=&quot;https://web.dev/reduce-javascript-payloads-with-code-splitting&quot;&gt;Reduce JavaScript payloads with code splitting&lt;/a&gt; guide first. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;The &lt;strong&gt;&lt;code&gt;React.lazy&lt;/code&gt;&lt;/strong&gt; method makes it easy to code-split a React application on a
component level using dynamic imports.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lazy &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AvatarComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./AvatarComponent&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;DetailsComponent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&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;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&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;&lt;span class=&quot;token class-name&quot;&gt;AvatarComponent&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&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;div&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 punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;why-is-this-useful&quot;&gt;Why is this useful? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/code-splitting-suspense/#why-is-this-useful&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A large React application will usually consist of many components, utility
methods, and third-party libraries. If an effort isn&#39;t made to try to load
different parts of an application only when they&#39;re needed, a single, large
bundle of JavaScript will be shipped to your users as soon as they load the
first page. This can affect page performance significantly.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;React.lazy&lt;/code&gt; function provides a built-in way to separate components in an
application into separate chunks of JavaScript with very little legwork. You can
then take care of loading states when you couple it with the &lt;code&gt;Suspense&lt;/code&gt;
component.&lt;/p&gt;
&lt;h2 id=&quot;suspense&quot;&gt;Suspense &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/code-splitting-suspense/#suspense&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The problem with shipping a large JavaScript payload to users is the length of
time it would take for the page to finish loading, especially on weaker devices
and network connections. This is why code splitting and lazy loading is
extremely useful.&lt;/p&gt;
&lt;p&gt;However, there will always be a slight delay that users have to experience when
a code-split component is being fetched over the network, so it&#39;s important to
display a useful loading state. Using &lt;code&gt;React.lazy&lt;/code&gt; with the &lt;strong&gt;&lt;code&gt;Suspense&lt;/code&gt;&lt;/strong&gt;
component helps solve this problem.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lazy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Suspense &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AvatarComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./AvatarComponent&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;renderLoader&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Loading&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;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;DetailsComponent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&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;&lt;span class=&quot;token class-name&quot;&gt;Suspense&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;fallback&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation 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;renderLoader&lt;/span&gt;&lt;span class=&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;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&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;&lt;span class=&quot;token class-name&quot;&gt;AvatarComponent&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&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;&lt;span class=&quot;token class-name&quot;&gt;Suspense&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;Suspense&lt;/code&gt; accepts a &lt;code&gt;fallback&lt;/code&gt; component which allows you to display any React
component as a loading state. The following example shows how this works.
The avatar is only rendered when the button is clicked, where a request is
then made to retrieve the code necessary for the suspended &lt;code&gt;AvatarComponent&lt;/code&gt;.
In the meantime, the fallback loading component is shown.&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/react-lazy-suspense?attributionHidden=true&amp;sidebarCollapsed=true&amp;path=src%2Findex.css&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;react-lazy-suspense on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;In here, the code that makes up &lt;code&gt;AvatarComponent&lt;/code&gt; is small which is
why the loading spinner only shows for a short amount of time. Larger
components can take much longer to load, especially on
weak network connections.&lt;/p&gt;
&lt;p&gt;To better demonstrate how this works:&lt;/p&gt;
&lt;ul&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;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;Network&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Throttling&lt;/strong&gt; dropdown, which is set to &lt;strong&gt;No throttling&lt;/strong&gt; by default. Select &lt;strong&gt;Fast 3G&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Click Me&lt;/strong&gt; button in the app.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The loading indicator will show for longer now. Notice how all the code that
makes up the &lt;code&gt;AvatarComponent&lt;/code&gt; is fetched as a separate chunk.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;DevTools network panel showing one chunk.js file being downloaded&quot; decoding=&quot;async&quot; height=&quot;478&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ga9IsnuJoJdnUfE6sGee.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; React does not currently support Suspense when components are being server-side rendered. If you are rendering on the server, consider using another library such as &lt;a href=&quot;https://www.smooth-code.com/open-source/loadable-components/docs/server-side-rendering/&quot;&gt;&lt;code&gt;loadable-components&lt;/code&gt;&lt;/a&gt; which is recommended in the React docs. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;suspending-multiple-components&quot;&gt;Suspending multiple components &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/code-splitting-suspense/#suspending-multiple-components&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another feature of &lt;code&gt;Suspense&lt;/code&gt; is that it allows you to suspend multiple
components from loading, &lt;strong&gt;even if they are all lazy loaded&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lazy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Suspense &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AvatarComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./AvatarComponent&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; InfoComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./InfoComponent&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; MoreInfoComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./MoreInfoComponent&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;renderLoader&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Loading&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;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;DetailsComponent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&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;&lt;span class=&quot;token class-name&quot;&gt;Suspense&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;fallback&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation 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;renderLoader&lt;/span&gt;&lt;span class=&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;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&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;&lt;span class=&quot;token class-name&quot;&gt;AvatarComponent&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&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;&lt;span class=&quot;token class-name&quot;&gt;InfoComponent&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&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;&lt;span class=&quot;token class-name&quot;&gt;MoreInfoComponent&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&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;&lt;span class=&quot;token class-name&quot;&gt;Suspense&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This is an extremely useful way to delay rendering of multiple components while
only showing a single loading state. Once all the components have finished
fetching, the user gets to see them all displayed at the same time.&lt;/p&gt;
&lt;p&gt;You can see this with the following embed:&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/react-lazy-suspense-multiple?attributionHidden=true&amp;sidebarCollapsed=true&amp;path=src%2Findex.css&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;react-lazy-suspense-multiple 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; Loading indicator showing a little too quickly? Try simulating a throttled connection in DevTools again. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Without this, it&#39;s easy to run into the problem of &lt;em&gt;staggered loading&lt;/em&gt;, or
different parts of a UI loading one after the other with each having their own
loading indicator. This can make the user experience feel more jarring.&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; Although using Suspense to split components is already possible and makes it easy to trim down bundle sizes, the React team is continuing to work on more features that would extend this even further. The &lt;a href=&quot;https://reactjs.org/blog/2018/11/27/react-16-roadmap.html&quot;&gt;React 16.x roadmap&lt;/a&gt; explains this in more detail. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;handle-loading-failures&quot;&gt;Handle loading failures &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/code-splitting-suspense/#handle-loading-failures&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Suspense&lt;/code&gt; allows you to display a temporary loading state while network
requests are made under the hood. But what if those network requests fail for
some reason? You might be offline, or perhaps your web app is attempting to
lazy-load a &lt;a href=&quot;https://web.dev/http-cache/#long-lived-caching-for-versioned-urls&quot;&gt;versioned URL&lt;/a&gt;
that is out of date, and no longer available following a server redeployment.&lt;/p&gt;
&lt;p&gt;React has a standard pattern for gracefully handling these types of loading
failures: using an error boundary. As described &lt;a href=&quot;https://reactjs.org/docs/error-boundaries.html&quot; rel=&quot;noopener&quot;&gt;in the documentation&lt;/a&gt;,
any React component can serve as an error boundary if it implements either (or
both) of the lifecycle methods &lt;code&gt;static getDerivedStateFromError()&lt;/code&gt; or
&lt;code&gt;componentDidCatch()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To detect and handle lazy loading failures, you can wrap your &lt;code&gt;Suspense&lt;/code&gt;
component with a parent components that serves as an error boundary. Inside the
error boundary&#39;s &lt;code&gt;render()&lt;/code&gt; method, you can render the children as-is if there&#39;s
no error, or render a custom error message if something goes wrong:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lazy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Suspense &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;react&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; AvatarComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./AvatarComponent&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; InfoComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./InfoComponent&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; MoreInfoComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./MoreInfoComponent&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;renderLoader&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Loading&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&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;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ErrorBoundary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Component&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&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;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;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;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;hasError&lt;/span&gt;&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;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;static&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDerivedStateFromError&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 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;span class=&quot;token literal-property property&quot;&gt;hasError&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hasError&lt;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 operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;Loading failed&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; Please reload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;p&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 punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;DetailsComponent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ErrorBoundary&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Suspense fallback&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;renderLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;AvatarComponent &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;InfoComponent &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;MoreInfoComponent &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Suspense&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;ErrorBoundary&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/code-splitting-suspense/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you are unsure where to begin applying code splitting to your React
application, follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Begin at the route level. Routes are the simplest way to identify points of
your application that can be split. The
&lt;a href=&quot;https://reactjs.org/docs/code-splitting.html#route-based-code-splitting&quot; rel=&quot;noopener&quot;&gt;React docs&lt;/a&gt;
show how &lt;code&gt;Suspense&lt;/code&gt; can be used along with
&lt;a href=&quot;https://github.com/ReactTraining/react-router&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;react-router&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Identify any large components on a page on your site that only render on
certain user interactions (like clicking a button). Splitting these
components will minimize your JavaScript payloads.&lt;/li&gt;
&lt;li&gt;Consider splitting anything else that is offscreen and not critical for the
user.&lt;/li&gt;
&lt;/ol&gt;
</content>
    <author>
      <name>Houssein Djirdeh</name>
    </author><author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>What is network reliability and how do you measure it?</title>
    <link href="https://web.dev/network-connections-unreliable/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/network-connections-unreliable/</id>
    <content type="html" mode="escaped">&lt;p&gt;The modern web is enjoyed by a wide swath of people, using a range of different
devices and types of network connections. Your creations can reach users all
across the world, but delivering a &lt;em&gt;reliable&lt;/em&gt; experience on the web for all of
your users can be challenging. It can be a challenge just to understand what
reliability means.&lt;/p&gt;
&lt;h2 id=&quot;reliable-while-offline&quot;&gt;Reliable while offline &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/network-connections-unreliable/#reliable-while-offline&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One way of thinking about reliability is whether your web app will work without
a network connection. This is a type of reliability that users take for granted
with platform-specific apps installed on a mobile device from an app store. When you see
an icon for one of these apps, you expect to be able to tap on it and open up some
sort of experience, regardless of whether you&#39;re currently connected to the
Internet.&lt;/p&gt;
&lt;p&gt;Until recently, it&#39;s been a challenge to build web applications that are
reliable without a network connection.&lt;/p&gt;
&lt;h2 id=&quot;reliably-fast&quot;&gt;Reliably fast &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/network-connections-unreliable/#reliably-fast&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another way of thinking about reliability is whether your users can rely on your
web app loading at a fast enough speed when they have a network connection that
might be less than ideal. Will returning users have the same experience
interacting with your web app when they&#39;re on a cellular connection as they do
when they&#39;re on wi-fi? And what about users who have a high-latency, or
&amp;quot;&lt;a href=&quot;https://web.dev/performance-poor-connectivity/#what-is-lie-fi&quot;&gt;lie-fi&lt;/a&gt;&amp;quot;
connection. Will your web app be reliably fast even in those scenarios?&lt;/p&gt;
&lt;p&gt;It&#39;s not enough to  be fast under the best circumstances. Your users will view
your web app&#39;s performance through the lens of how it behaves in all network
conditions.&lt;/p&gt;
&lt;h2 id=&quot;reliable-is-achievable&quot;&gt;Reliable is achievable &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/network-connections-unreliable/#reliable-is-achievable&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The good news is that the modern web platform provides technologies—such as
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Service_Worker_API&quot; rel=&quot;noopener&quot;&gt;service workers&lt;/a&gt; and the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/CacheStorage&quot; rel=&quot;noopener&quot;&gt;Cache Storage API&lt;/a&gt;—that
can serve as the building blocks for creating reliable web applications. They
allow you to write code which sits between your web app and the network. In
many cases, you can bypass the network entirely, and instead use previously
cached content to fulfill your web app&#39;s requests.&lt;/p&gt;
&lt;h2 id=&quot;your-guiding-light-responds-with-a-200-ok-while-offline&quot;&gt;Your guiding light: Responds with a 200 OK while offline &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/network-connections-unreliable/#your-guiding-light-responds-with-a-200-ok-while-offline&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once you start building out a service worker and serving content from caches,
it&#39;s hard to know if you&#39;re doing it effectively. How do you know that the
service worker you implement really does help your web app avoid the network?
How do you prevent a small change to your caching strategy from breaking your
carefully crafted offline experience?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot; rel=&quot;noopener&quot;&gt;Lighthouse&lt;/a&gt; provides one
specific test that is of particular interest when building a reliable web app:
&lt;strong&gt;Responds with a 200 OK while offline&lt;/strong&gt;:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Lighthouse&amp;#x27;s progressive web app report showing a passing responds with a 200 when offline audit.&quot; decoding=&quot;async&quot; height=&quot;253&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5bc5TNicZiBgDdWkgAXg.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The actual interface may differ depending on which version of Lighthouse you&#39;re running. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;What&#39;s actually being tested here? It boils down to simulating a loss of network
connectivity within your browser, followed by an attempt to load whichever URL
on your site is being audited. This tests one aspect of building a reliable
site—being &lt;em&gt;reliable while offline&lt;/em&gt;—using a controlled, repeatable sequence of
actions.&lt;/p&gt;
&lt;h2 id=&quot;its-a-journey&quot;&gt;It&#39;s a journey &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/network-connections-unreliable/#its-a-journey&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;re just starting out, then there&#39;s a very good chance that you&#39;ll get
back a negative result for the Responds with a 200 while offline check. That&#39;s
okay!  Unless you&#39;re using a customized starter project, web applications don&#39;t
have that type of reliability by default. The next few guides will introduce the
techniques you need to identify what your web app is loading, and teach you how
to use Lighthouse to make that loading experience reliable.&lt;/p&gt;
&lt;p&gt;Throughout this process, you&#39;re encouraged to keep re-running the Lighthouse
audits. They serve as a guiding light throughout your journey, starting with a new
web application and ending with a reliable progressive web app.&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Prevent unnecessary network requests with the HTTP Cache</title>
    <link href="https://web.dev/http-cache/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/http-cache/</id>
    <content type="html" mode="escaped">&lt;p&gt;Fetching resources over the network is both slow and expensive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Large responses require many roundtrips between the browser and the server.&lt;/li&gt;
&lt;li&gt;Your page won&#39;t load until all of its &lt;a href=&quot;https://web.dev/critical-rendering-path/&quot;&gt;critical resources&lt;/a&gt; have downloaded completely.&lt;/li&gt;
&lt;li&gt;If a person is accessing your site with a limited mobile data plan, every unnecessary
network request is a waste of their money.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How can you avoid unnecessary network requests? The browser&#39;s HTTP Cache is your
first line of defense. It&#39;s not necessarily the most powerful or flexible
approach, and you have limited control over the lifetime of cached responses,
but it&#39;s effective, it&#39;s supported in all browsers, and it doesn&#39;t require much
work.&lt;/p&gt;
&lt;p&gt;This guide shows you the basics of an effective HTTP caching implementation.&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/http-cache/#browser-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There isn&#39;t actually a single API called the HTTP Cache. It&#39;s the general name
for a collection of web platform APIs. Those APIs are supported in all browsers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Cache-Control&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ETag&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Last-Modified#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Last-Modified&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;overview&quot;&gt;How the HTTP Cache works &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#overview&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All HTTP requests that the browser makes are first routed to the browser cache
to check whether there is a valid cached response that can be used to fulfill
the request. If there&#39;s a match, the response is read from the cache, which
eliminates both the network latency and the data costs that the transfer incurs.&lt;/p&gt;
&lt;p&gt;The HTTP Cache&#39;s behavior is controlled by a combination of
&lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Request_header&quot; rel=&quot;noopener&quot;&gt;request headers&lt;/a&gt; and
&lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Response_header&quot; rel=&quot;noopener&quot;&gt;response headers&lt;/a&gt;.
In an ideal scenario, you&#39;ll have control over both the code for your
web application (which will determine the request headers) and your web server&#39;s
configuration (which will determine the response headers).&lt;/p&gt;
&lt;p&gt;Check out MDN&#39;s &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Caching&quot; rel=&quot;noopener&quot;&gt;HTTP Caching&lt;/a&gt; article
for a more in-depth conceptual overview.&lt;/p&gt;
&lt;h2 id=&quot;request-headers&quot;&gt;Request headers: stick with the defaults (usually) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#request-headers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While there are a number of important headers that should be included in your
web app&#39;s outgoing requests, the browser almost always takes care of setting
them on your behalf when it makes requests. Request headers that affect checking
for freshness, like &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/If-None-Match&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;If-None-Match&lt;/code&gt;&lt;/a&gt; and
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/If-Modified-Since&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;If-Modified-Since&lt;/code&gt;&lt;/a&gt; just appear based on the browser&#39;s
understanding of the current values in the HTTP Cache.&lt;/p&gt;
&lt;p&gt;This is good news—it means that you can continue including tags like &lt;code&gt;&amp;lt;img src=&amp;quot;my-image.png&amp;quot;&amp;gt;&lt;/code&gt; in your HTML, and the browser  automatically takes care of
HTTP caching for you, without extra effort.&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; Developers who do need more control over the HTTP Cache in their web application have an alternative—you can &amp;quot;drop down&amp;quot; a level, and manually use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Fetch_API&quot;&gt;Fetch API&lt;/a&gt;, passing it &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Request&quot;&gt;&lt;code&gt;Request&lt;/code&gt;&lt;/a&gt; objects with specific &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Request/cache&quot;&gt;&lt;code&gt;cache&lt;/code&gt;&lt;/a&gt; overrides set. That&#39;s beyond the scope of this guide, though! &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;response-headers&quot;&gt;Response headers: configure your web server &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#response-headers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The part of the HTTP caching setup that matters the most is the headers that
your web server adds to each outgoing response. The following headers all factor
into effective caching behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Cache-Control&lt;/code&gt;&lt;/a&gt;.
The server can return a &lt;code&gt;Cache-Control&lt;/code&gt; directive to specify how, and for how
long, the browser and other intermediate caches should cache the individual
response.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ETag&lt;/code&gt;&lt;/a&gt;. When
the browser finds an expired cached response, it can send a small token
(usually a hash of the file&#39;s contents) to the server to check if the file has
changed. If the server returns the same token, then the file is the same, and there&#39;s
no need to re-download it.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Last-Modified&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Last-Modified&lt;/code&gt;&lt;/a&gt;.
This header serves the same purpose as &lt;code&gt;ETag&lt;/code&gt;, but uses a time-based strategy
to determine if a resource has changed, as opposed to the content-based strategy
of &lt;code&gt;ETag&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some web servers have built-in support for setting those headers by default,
while others leave the headers out entirely unless you explicitly configure
them. The specific details of &lt;em&gt;how&lt;/em&gt; to configure headers varies greatly
depending on which web server you use, and you should consult your server&#39;s
documentation to get the most accurate details.&lt;/p&gt;
&lt;p&gt;To save you some searching, here are instructions on configuring a few popular
web servers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://expressjs.com/en/api.html#express.static&quot; rel=&quot;noopener&quot;&gt;Express&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://httpd.apache.org/docs/2.4/caching.html&quot; rel=&quot;noopener&quot;&gt;Apache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_headers_module.html&quot; rel=&quot;noopener&quot;&gt;nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://firebase.google.com/docs/hosting/full-config&quot; rel=&quot;noopener&quot;&gt;Firebase Hosting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.netlify.com/blog/2017/02/23/better-living-through-caching/&quot; rel=&quot;noopener&quot;&gt;Netlify&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Leaving out the &lt;code&gt;Cache-Control&lt;/code&gt; response header does not disable HTTP caching!
Instead, browsers &lt;a href=&quot;https://www.mnot.net/blog/2017/03/16/browser-caching#heuristic-freshness&quot; rel=&quot;noopener&quot;&gt;effectively
guess&lt;/a&gt;
what type of caching behavior makes the most sense for a given type of content.
Chances are you want more control than that offers, so take the time to
configure your response headers.&lt;/p&gt;
&lt;h2 id=&quot;response-header-strategies&quot;&gt;Which response header values should you use? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#response-header-strategies&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are two important scenarios that you should cover when configuring your
web server&#39;s response headers.&lt;/p&gt;
&lt;h3 id=&quot;versioned-urls&quot;&gt;Long-lived caching for versioned URLs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#versioned-urls&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;details&gt;
  &lt;summary&gt;
  How versioned URLs can help your caching strategy
  &lt;p class=&quot;text-base color-core-text gap-top-base&quot;&gt;    Versioned URLs are a good practice because they make it easier to invalidate cached responses.&lt;/p&gt;
&lt;/summary&gt;
  Suppose your server instructs browsers to cache a CSS file
  for 1 year (&lt;code&gt;Cache-Control: max-age=31536000&lt;/code&gt;) but your designer just made an
  emergency update that you need to roll out immediately. How do you notify browsers
  to update the &quot;stale&quot; cached copy of the file?
  You can&#39;t, at least not without changing the URL of the resource. After the
  browser caches the response, the cached version is used until it&#39;s no longer
  fresh, as determined by &lt;code&gt;max-age&lt;/code&gt; or &lt;code&gt;expires&lt;/code&gt;, or until it is evicted from the cache
  for some other reason—for example, the user clearing their browser cache. As a
  result, different users might end up using different versions of the file when
  the page is constructed: users who just fetched the resource use the new
  version, while users who cached an earlier (but still valid) copy use an older
  version of its response. How do you get the best of both worlds: client-side
  caching and quick updates? You change the URL of the resource and force the user
  to download the new response whenever its content changes. Typically, you do
  this by embedding a fingerprint of the file, or a version number, in its
  filename—for example, &lt;code&gt;style.x234dff.css&lt;/code&gt;.
&lt;/details&gt;
&lt;p&gt;When responding to requests for URLs that contain
&amp;quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/Fingerprint_(computing)&quot; rel=&quot;noopener&quot;&gt;fingerprint&lt;/a&gt;&amp;quot; or
versioning information, and whose contents are never meant to change, add
&lt;code&gt;Cache-Control: max-age=31536000&lt;/code&gt; to your responses.&lt;/p&gt;
&lt;p&gt;Setting this value tells the browser that when it needs to load the same URL
anytime over the next one year (31,536,000 seconds; the maximum supported
value), it can immediately use the value in the HTTP Cache, without having
to make a network request to your web server at all. That&#39;s great—you&#39;ve
immediately gained the reliability and speed that comes from avoiding the
network!&lt;/p&gt;
&lt;p&gt;Build tools like webpack can
&lt;a href=&quot;https://webpack.js.org/guides/caching/#output-filenames&quot; rel=&quot;noopener&quot;&gt;automate the process&lt;/a&gt;
of assigning hash fingerprints to your asset URLs.&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; You can also add the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control#Revalidation_and_reloading&quot;&gt;&lt;code&gt;immutable&lt;/code&gt; property&lt;/a&gt; to your &lt;code&gt;Cache-Control&lt;/code&gt; header as a further optimization, though it &lt;a href=&quot;https://www.keycdn.com/blog/cache-control-immutable#browser-support&quot;&gt;will be ignored&lt;/a&gt; in some browsers. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;unversioned-urls&quot;&gt;Server revalidation for unversioned URLs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#unversioned-urls&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unfortunately, not all of the URLs you load are versioned. Maybe you&#39;re not able
to include a build step prior to deploying your web app, so you can&#39;t add hashes
to your asset URLs. And every web application needs HTML files—those files are
(almost!) never going to include versioning information, since no one will
bother to use your web app if they need to remember that the URL to visit is
&lt;code&gt;https://example.com/index.34def12.html&lt;/code&gt;. So what can you do for those URLs?&lt;/p&gt;
&lt;p&gt;This is one scenario in which you need to admit defeat. HTTP caching alone isn&#39;t
powerful enough to avoid the network completely. (Don&#39;t worry—you&#39;ll soon learn
about &lt;a href=&quot;https://web.dev/service-workers-cache-storage/&quot;&gt;service workers&lt;/a&gt;, which will provide the
support we need to swing the battle back in your favor.) But there are a few
steps you can take to make sure that network requests are as quick and efficient
as possible.&lt;/p&gt;
&lt;p&gt;The following &lt;code&gt;Cache-Control&lt;/code&gt; values can help you fine-tune where and how unversioned URLs
are cached:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;no-cache&lt;/code&gt;. This instructs the browser that it must revalidate with the
server every time before using a cached version of the URL.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;no-store&lt;/code&gt;. This instructs the browser and other intermediate caches (like CDNs) to never
store any version of the file.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;private&lt;/code&gt;. Browsers can cache the file but intermediate caches cannot.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public&lt;/code&gt;. The response can be stored by any cache.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://web.dev/http-cache/#flowchart&quot;&gt;Appendix: &lt;code&gt;Cache-Control&lt;/code&gt; flowchart&lt;/a&gt; to visualize the process
of deciding which &lt;code&gt;Cache-Control&lt;/code&gt; value(s) to use. Note also that &lt;code&gt;Cache-Control&lt;/code&gt; can
accept a comma-separated list of directives. See &lt;a href=&quot;https://web.dev/http-cache/#examples&quot;&gt;Appendix: &lt;code&gt;Cache-Control&lt;/code&gt; examples&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Along with that, setting one of two additional response headers can also help:
either &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ETag&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Last-Modified&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Last-Modified&lt;/code&gt;&lt;/a&gt;. As mentioned in
&lt;a href=&quot;https://web.dev/http-cache/#response-headers&quot;&gt;Response headers&lt;/a&gt;, &lt;code&gt;ETag&lt;/code&gt; and &lt;code&gt;Last-Modified&lt;/code&gt; both serve the
same purpose: determining if the browser needs to re-download a cached file
that has expired. &lt;code&gt;ETag&lt;/code&gt; is the recommended approach because it&#39;s more accurate.&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;
  ETag example
&lt;/summary&gt;
  Assume that 120 seconds have passed since the initial fetch and the browser
  has initiated a new request for the same resource. First, the browser checks
  the HTTP Cache and finds the previous response. Unfortunately, the browser
  can&#39;t use the previous response because the response has now expired. At this
  point, the browser could dispatch a new request and fetch the new full
  response. However, that&#39;s inefficient because if the resource hasn&#39;t changed,
  then there&#39;s no reason to download the same information that&#39;s already in the
  cache! That&#39;s the problem that validation tokens, as specified in the &lt;code&gt;ETag&lt;/code&gt;
  header, are designed to solve. The server generates and returns an arbitrary
  token, which is typically a hash or some other fingerprint of the contents of
  the file. The browser doesn&#39;t need to know how the fingerprint is generated; it
  only needs to send it to the server on the next request. If the fingerprint is
  still the same, then the resource hasn&#39;t changed and the browser can skip the
  download.
&lt;/details&gt;
&lt;p&gt;By setting &lt;code&gt;ETag&lt;/code&gt; or &lt;code&gt;Last-Modified&lt;/code&gt;, you&#39;ll end up making the
revalidation request much more efficient. They end up triggering the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/If-Modified-Since&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;If-Modified-Since&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/If-None-Match&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;If-None-Match&lt;/code&gt;&lt;/a&gt;
request headers that were mentioned in &lt;a href=&quot;https://web.dev/http-cache/#request-headers&quot;&gt;Request headers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When a properly configured web server sees those incoming request headers, it
can confirm whether the version of the resource that the browser already has in
its HTTP Cache matches the latest version on the web server. If there&#39;s a match,
then the server can respond with a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Status/304&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;304 Not Modified&lt;/code&gt;&lt;/a&gt; HTTP response,
which is the equivalent of &amp;quot;Hey, keep using what you&#39;ve already got!&amp;quot; There&#39;s
very little data to transfer when sending this type of response, so it&#39;s usually
much faster than having to actually send back a copy of the actual resource
being requested.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A diagram of a client requesting a resource and the server responding with a 304 header.&quot; decoding=&quot;async&quot; height=&quot;215&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 474px) 474px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png?auto=format&amp;w=948 948w&quot; width=&quot;474&quot; /&gt;
  &lt;figcaption&gt;
    The browser requests &lt;code&gt;/file&lt;/code&gt; from the server and includes the &lt;code&gt;If-None-Match&lt;/code&gt;
    header to instruct the server to only return the full file if the &lt;code&gt;ETag&lt;/code&gt; of
    the file on the server doesn&#39;t match the browser&#39;s &lt;code&gt;If-None-Match&lt;/code&gt; value. In this
    case, the 2 values did match, so the server returns a &lt;code&gt;304 Not Modified&lt;/code&gt; response
    with instructions on how much longer the file should be cached (&lt;code&gt;Cache-Control: max-age=120&lt;/code&gt;).
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#summary&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The HTTP Cache is an effective way to improve load performance because
it reduces unnecessary network requests. It&#39;s supported in all browsers and doesn&#39;t
take too much work to set up.&lt;/p&gt;
&lt;p&gt;The following &lt;code&gt;Cache-Control&lt;/code&gt; configurations are a good start:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Cache-Control: no-cache&lt;/code&gt; for resources that should be revalidated with the server before every use.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cache-Control: no-store&lt;/code&gt; for resources that should never be cached.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cache-Control: max-age=31536000&lt;/code&gt; for versioned resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the &lt;code&gt;ETag&lt;/code&gt; or &lt;code&gt;Last-Modified&lt;/code&gt; header can help you revalidate expired cache resources more efficiently.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-quaternary-box-bg color-quaternary-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; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Code brackets&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;M9.41 16.59L8 18l-6-6 6-6 1.41 1.41L4.83 12l4.58 4.59zm5.18-9.18L16 6l6 6-6 6-1.41-1.41L19.17 12l-4.58-4.59z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Try it&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Try the &lt;a href=&quot;https://web.dev/codelab-http-cache&quot;&gt;HTTP Cache codelab&lt;/a&gt; to get hands-on experience with &lt;code&gt;Cache-Control&lt;/code&gt; and &lt;code&gt;ETag&lt;/code&gt; in Express. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;learn-more&quot;&gt;Learn more &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#learn-more&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;re looking to go beyond the basics of using the &lt;code&gt;Cache-Control&lt;/code&gt; header,
check out Jake Archibald&#39;s &lt;a href=&quot;https://jakearchibald.com/2016/caching-best-practices/&quot; rel=&quot;noopener&quot;&gt;Caching best practices &amp;amp; max-age
gotchas&lt;/a&gt; guide.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://web.dev/love-your-cache&quot;&gt;Love your cache&lt;/a&gt; for guidance on how to optimize
your cache usage for return visitors.&lt;/p&gt;
&lt;h2 id=&quot;tips&quot;&gt;Appendix: More tips &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#tips&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you have more time, here are further ways that you can optimize your usage of the HTTP Cache:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use consistent URLs. If you serve the same content on different URLs, then
that content will be fetched and stored multiple times.&lt;/li&gt;
&lt;li&gt;Minimize churn. If part of a resource (such as a CSS file) updates frequently, whereas the
rest of the file does not (such as library code), consider splitting the frequently updating
code into a separate file and using a short duration caching strategy for the frequently
updating code and a long caching duration strategy for the code that doesn&#39;t change often.&lt;/li&gt;
&lt;li&gt;Check out the new &lt;a href=&quot;https://web.dev/stale-while-revalidate/&quot;&gt;&lt;code&gt;stale-while-revalidate&lt;/code&gt;&lt;/a&gt; directive if
some degree of staleness is acceptable in your &lt;code&gt;Cache-Control&lt;/code&gt; policy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;flowchart&quot;&gt;Appendix: &lt;code&gt;Cache-Control&lt;/code&gt; flowchart &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#flowchart&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;img alt=&quot;Flowchart&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 595px) 595px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/htXr84PI8YR0lhgLPiqZ.png?auto=format&amp;w=1190 1190w&quot; width=&quot;595&quot; /&gt;
&lt;h2 id=&quot;examples&quot;&gt;Appendix: &lt;code&gt;Cache-Control&lt;/code&gt; examples &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/http-cache/#examples&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;&lt;code&gt;Cache-Control&lt;/code&gt; value&lt;/th&gt;
        &lt;th&gt;Explanation&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code&gt;max-age=86400&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;
          The response can be cached by browsers and intermediary caches for
          up to 1 day (60 seconds x 60 minutes x 24 hours).
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code&gt;private, max-age=600&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;
          The response can be cached by the browser (but not intermediary caches) for up to 10
          minutes (60 seconds x 10 minutes).
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code&gt;public, max-age=31536000&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;
          The response can be stored by any cache for 1 year.
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code&gt;no-store&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;
          The response is not allowed to be cached and must be fetched in full on every request.
        &lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author><author>
      <name>Ilya Grigorik</name>
    </author>
  </entry>
  
  <entry>
    <title>Service workers and the Cache Storage API</title>
    <link href="https://web.dev/service-workers-cache-storage/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/service-workers-cache-storage/</id>
    <content type="html" mode="escaped">&lt;p&gt;You&#39;re locked in a struggle for network reliability. The browser&#39;s HTTP cache is
your first line of defense, but as you&#39;ve learned, it&#39;s only really effective
when loading versioned URLs that you&#39;ve previously visited. On its own, the HTTP
cache is not enough.&lt;/p&gt;
&lt;p&gt;Fortunately, two newer tools are available to help turn the tide in your favor:
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Service_Worker_API&quot; rel=&quot;noopener&quot;&gt;service workers&lt;/a&gt;
and the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/CacheStorage&quot; rel=&quot;noopener&quot;&gt;Cache Storage API&lt;/a&gt;.
Since they&#39;re so often used together, it&#39;s worth learning about them both at the
same time.&lt;/p&gt;
&lt;h2 id=&quot;service-workers&quot;&gt;Service workers &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-cache-storage/#service-workers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A service worker is built into the browser and controlled by a bit of extra
JavaScript code that you are responsible for creating. You deploy this alongside
the other files that make up your actual web application.&lt;/p&gt;
&lt;p&gt;A service worker has some special powers. Among other duties, it patiently waits
for your web app to make an outgoing request, and then springs into action by
intercepting it. What the service worker does with this intercepted request is
up to you.&lt;/p&gt;
&lt;p&gt;For some requests, the best course of action might be just to allow the request
to continue on to the network, just like what would happen if there were no
service worker at all.&lt;/p&gt;
&lt;p&gt;For other requests, though, you can take advantage of something more flexible
than the browser&#39;s HTTP cache, and return a reliably fast response without
having to worry about the network. That entails using the other piece of the
puzzle: the Cache Storage API.&lt;/p&gt;
&lt;h2 id=&quot;the-cache-storage-api&quot;&gt;The Cache Storage API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-cache-storage/#the-cache-storage-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 43, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      43
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 41, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      41
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 16, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      16
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 11.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      11.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/CacheStorage#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;The Cache Storage  API opens up a whole new range of possibilities, by giving
developers complete control over the contents of the cache. Instead of relying
on a combination of HTTP headers and the browser&#39;s built-in &lt;a href=&quot;https://httpwg.org/specs/rfc7234.html#heuristic.freshness&quot; rel=&quot;noopener&quot;&gt;heuristics&lt;/a&gt;,
the Cache
Storage API exposes a code-driven approach to caching. The Cache Storage API
is particularly useful when called from your service worker&#39;s JavaScript.&lt;/p&gt;
&lt;h3 id=&quot;wait-theres-another-cache-to-think-about-now&quot;&gt;Wait… there&#39;s another cache to think about now? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-cache-storage/#wait-theres-another-cache-to-think-about-now&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You&#39;re probably asking yourself questions like &amp;quot;Do I still need to configure my
HTTP headers?&amp;quot; and &amp;quot;What can I do with this new cache that wasn&#39;t possible with
the HTTP cache?&amp;quot; Don&#39;t worry—those are natural reactions.&lt;/p&gt;
&lt;p&gt;It&#39;s still recommended that you configure the &lt;code&gt;Cache-Control&lt;/code&gt; headers on your web
server, even when you know that you&#39;re using the Cache Storage API. You can
usually get away with setting &lt;code&gt;Cache-Control: no-cache&lt;/code&gt; for unversioned URLs,
and/or &lt;code&gt;Cache-Control: max-age=31536000&lt;/code&gt; for URLs that contain versioning
information, like hashes.&lt;/p&gt;
&lt;p&gt;When populating the Cache Storage API cache, the browser
&lt;a href=&quot;https://jakearchibald.com/2016/caching-best-practices/#the-service-worker-the-http-cache-play-well-together-dont-make-them-fight&quot; rel=&quot;noopener&quot;&gt;defaults to checking for existing entries&lt;/a&gt;
in the HTTP cache, and uses those if found. If you&#39;re adding versioned URLs to
the Cache Storage API cache, the browser avoids an additional network request. The
flip side of this is that if you&#39;re using misconfigured &lt;code&gt;Cache-Control&lt;/code&gt; headers,
like specifying a long-lived cache lifetime for an unversioned URL, you can end
up
&lt;a href=&quot;https://jakearchibald.com/2016/caching-best-practices/#a-service-worker-can-extend-the-life-of-these-bugs&quot; rel=&quot;noopener&quot;&gt;making things worse&lt;/a&gt;
by adding that stale content to the Cache Storage API. Getting your HTTP cache
behavior sorted is a prerequisite for effectively using the Cache Storage API.&lt;/p&gt;
&lt;p&gt;As for what&#39;s now possible with this new API, the answer is: a lot. Some common
uses that would be difficult or impossible with just the HTTP cache include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use a &amp;quot;refresh in the background&amp;quot; approach to cached content, known as
stale-while-revalidate.&lt;/li&gt;
&lt;li&gt;Impose a cap on the maximum number of assets to cache, and implement a
custom expiration policy to remove items once that limit is reached.&lt;/li&gt;
&lt;li&gt;Compare previously cached and fresh network responses to see if
anything&#39;s changed, and enable the user to update content (with a button,
for example)  when data has actually been updated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://web.dev/cache-api-quick-guide/&quot;&gt;The Cache API: A quick guide&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;h3 id=&quot;api-nuts-and-bolts&quot;&gt;API nuts and bolts &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-cache-storage/#api-nuts-and-bolts&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are some things to keep in mind about the API&#39;s design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Request&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Request&lt;/code&gt;&lt;/a&gt;
objects are used as the unique keys when reading and writing to these
caches. As a convenience, you can pass in a URL string like
&lt;code&gt;&#39;https://example.com/index.html&#39;&lt;/code&gt; as the key instead of an actual
&lt;code&gt;Request&lt;/code&gt; object, and the API will automatically handle that for you.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Response&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Response&lt;/code&gt;&lt;/a&gt;
objects are used as the values in these caches.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Cache-Control&lt;/code&gt; header on a given &lt;code&gt;Response&lt;/code&gt; is effectively ignored
when caching data. There are no automatic, built-in expiration or freshness
checks, and once you store an item in the cache, it will persist until your
code explicitly removes it. (There are libraries to simplify cache
maintenance on your behalf. They&#39;ll be covered later on in this series.)&lt;/li&gt;
&lt;li&gt;Unlike with older, synchronous APIs such as
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Storage/LocalStorage&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;LocalStorage&lt;/code&gt;&lt;/a&gt;,
all Cache Storage API operations are asynchronous.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;a-quick-detour-promises-and-asyncawait&quot;&gt;A quick detour: promises and async/await &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-cache-storage/#a-quick-detour-promises-and-asyncawait&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Service workers and the Cache Storage API use
&lt;a href=&quot;https://en.wikipedia.org/wiki/Asynchrony_(computer_programming)&quot; rel=&quot;noopener&quot;&gt;asynchronous programming concepts&lt;/a&gt;.
In particular, they rely heavily on promises to represent the future result of
async operations. You should familiarize yourself with
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise&quot; rel=&quot;noopener&quot;&gt;promises&lt;/a&gt;,
and the related
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;async&lt;/code&gt;&lt;/a&gt;/&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/await&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;await&lt;/code&gt;&lt;/a&gt;
syntax, before diving in.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-quaternary-box-bg color-quaternary-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; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Code brackets&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;M9.41 16.59L8 18l-6-6 6-6 1.41 1.41L4.83 12l4.58 4.59zm5.18-9.18L16 6l6 6-6 6-1.41-1.41L19.17 12l-4.58-4.59z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Try it&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;a href=&quot;https://web.dev/codelab-service-workers&quot;&gt;Make an application reliable by registering a service worker&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;dont-deploy-that-code-yet&quot;&gt;Don&#39;t deploy that code… yet &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-cache-storage/#dont-deploy-that-code-yet&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While they provide an important foundation, and can be used as-is, both service
workers and the Cache Storage API are effectively lower-level building blocks,
with a number of edge cases and &amp;quot;gotchas&amp;quot;. There are some higher-level tools
that can help smooth the difficult bits of those APIs, and provide all you need
to build a production-ready service worker. The next guide covers one such tool:
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt;.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Learn while having fun. Check out the new &lt;a href=&quot;https://serviceworkies.com/&quot;&gt;Service Workies game!&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Explore DevTools Network panel</title>
    <link href="https://web.dev/codelab-explore-network-panel/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/codelab-explore-network-panel/</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;This codelab walks you through the process of interpreting all of the network
traffic for a somewhat complex sample application. At the end of the exercise,
you&#39;ll have the skills you need to figure out &lt;em&gt;what&lt;/em&gt; your own web application is
loading and &lt;em&gt;when&lt;/em&gt; it&#39;s making each request.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The screenshots and instructions in this codelab assume that you&#39;re using Chrome. Each browser has its own DevTools experience, which might not match what you see in this codelab. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;navigate-to-the-network-panel&quot;&gt;Navigate to the Network Panel &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-explore-network-panel/#navigate-to-the-network-panel&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Navigate to the Network panel to see the network traffic for the demo
application.&lt;/p&gt;
&lt;ol&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;Press `Control+Shift+J` (or `Command+Option+J` on Mac) to open DevTools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click the &lt;strong&gt;Network&lt;/strong&gt; tab.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reload the page to see the network traffic.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Network panel shows all the assets loaded because of your initial
navigation:&lt;/p&gt;
&lt;img alt=&quot;Chrome DevTools&amp;#x27; network panel.&quot; class=&quot;screenshot&quot; decoding=&quot;async&quot; height=&quot;219&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kofLXSq1f3ekY7KY9QK7.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The actual columns you see in the Network panel may be different; the screenshot shows a simplified view with everything but the &lt;strong&gt;Name&lt;/strong&gt;, &lt;strong&gt;Type&lt;/strong&gt;, and &lt;strong&gt;Waterfall&lt;/strong&gt; columns hidden. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;how-to-interpret-the-entries&quot;&gt;How to interpret the entries &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-explore-network-panel/#how-to-interpret-the-entries&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Each row of recorded network traffic represents a single request and response
pair.&lt;/p&gt;
&lt;p&gt;The first row, with type &lt;code&gt;document&lt;/code&gt;, is the initial navigation request for the
web app&#39;s HTML. This is the source for the waterfall; each of the subsequent
requests for additional assets (known as subresources of the main document) flow
from this original source.&lt;/p&gt;
&lt;p&gt;The second and third rows, showing a CSS &lt;code&gt;stylesheet&lt;/code&gt; and a &lt;code&gt;script&lt;/code&gt; subresource
being loaded, are dependent requests that were initiated by the main document.&lt;/p&gt;
&lt;p&gt;Looking at &lt;em&gt;when&lt;/em&gt; those requests are made, the waterfall diagram shows that
they&#39;re not started until very late in the process of responding to the
navigation request.&lt;/p&gt;
&lt;p&gt;Taken together, the requests for the HTML document, CSS, and
JavaScript are needed to display the full page during the
initial navigation.&lt;/p&gt;
&lt;h2 id=&quot;create-some-additional-runtime-requests&quot;&gt;Create some additional runtime requests &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-explore-network-panel/#create-some-additional-runtime-requests&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the &lt;strong&gt;Network&lt;/strong&gt; panel still open and recording, it&#39;s time to simulate something
common for a lot of web apps: additional API requests used to add more data to
the page after the initial navigation is complete.&lt;/p&gt;
&lt;p&gt;Trigger these additional requests by clicking &lt;strong&gt;Find Me&lt;/strong&gt; in the app and then
&lt;strong&gt;Allow&lt;/strong&gt; in the popup that appears.
This will allow the site to access your current location:&lt;/p&gt;
&lt;img alt=&quot;The allow location permission prompt.&quot; decoding=&quot;async&quot; height=&quot;257&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 638px) 638px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TJTq6re6eiVf74N8SwWE.png?auto=format&amp;w=1276 1276w&quot; width=&quot;638&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 could also deny geolocation permission, in which case the web app will fall back to a default location. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Once the web app has a location to work with, clicking &lt;strong&gt;Find Nearby
Wikipedia Entries&lt;/strong&gt; results in several additional network requests. You
should see something like this:&lt;/p&gt;
&lt;img alt=&quot;image&quot; decoding=&quot;async&quot; height=&quot;567&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/Y9EAf75LBCkkpXyatG3f.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;interpret-the-new-entries&quot;&gt;Interpret the new entries &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-explore-network-panel/#interpret-the-new-entries&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As before, each row of recorded network traffic represents a single request
and response pair.&lt;/p&gt;
&lt;p&gt;The first row of the new entries represents a request with a type of &lt;code&gt;fetch&lt;/code&gt;,
which corresponds to the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Fetch_API&quot; rel=&quot;noopener&quot;&gt;way the web app requests data&lt;/a&gt;
from the Wikipedia API.&lt;/p&gt;
&lt;p&gt;The following rows all are images (&lt;code&gt;png&lt;/code&gt; or &lt;code&gt;jpeg&lt;/code&gt;) associated with the
Wikipedia entries. Although it&#39;s a little hard to see from the screenshot, their
entries in the Waterfall column directly flow from the API response.&lt;/p&gt;
&lt;p&gt;For all of these additional requests, the &lt;em&gt;when&lt;/em&gt; is going to vary based on how
long you&#39;ve had the page open before you click on &lt;strong&gt;Find Nearby Wikipedia
Entries&lt;/strong&gt;. Most important here is that the &lt;em&gt;when&lt;/em&gt; is disconnected from the
initial navigation request. You can tell this from the large gap that exists in
the Waterfall display, representing a period of time that passed in between the
initial loading and when the Wikipedia API request is made.&lt;/p&gt;
&lt;p&gt;Requests made after a gap of time following a navigation fall into the category
of &amp;quot;runtime requests,&amp;quot; as opposed to the initial set of requests used to display
the page when you first navigated to it.&lt;/p&gt;
&lt;h2 id=&quot;summing-things-up&quot;&gt;Summing things up &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-explore-network-panel/#summing-things-up&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Having gone through the steps in this codelab, you&#39;re now familiar with the
tools you can use to analyze what &lt;em&gt;any&lt;/em&gt; web application loads.&lt;/p&gt;
&lt;p&gt;The Network panel helps you answer the question of &lt;em&gt;what&lt;/em&gt;&#39;s being loaded, via
the URLs in the Name column and the data in the Type column, along with &lt;em&gt;when&lt;/em&gt;
it&#39;s being loaded, via the waterfall display.&lt;/p&gt;
&lt;p&gt;You&#39;ve also seen that requests made by a web page can (usually) be grouped into
one of two categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Initial requests, made right after navigating to a new page, for the
HTML, JavaScript, CSS (and potentially other assets).&lt;/li&gt;
&lt;li&gt;Runtime requests made as a result of user interaction with the page. This
can often start with a request to an API, and then flow into several
follow-up requests based on the API data retrieved.&lt;/li&gt;
&lt;/ol&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Configuring HTTP caching behavior</title>
    <link href="https://web.dev/codelab-http-cache/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/codelab-http-cache/</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;This codelab shows you how to change the HTTP caching headers returned by a
Node.js-based web server, running the &lt;a href=&quot;https://expressjs.com/&quot; rel=&quot;noopener&quot;&gt;Express&lt;/a&gt; serving
framework. It will also show how to confirm that the caching behavior you expect
is actually being applied, using the Network panel in Chrome&#39;s DevTools.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; While the specific instructions are tailored towards Express, the general principles about choosing the correct caching headers apply to any web server environment. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;get-familiar-with-the-sample-project&quot;&gt;Get familiar with the sample project &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-http-cache/#get-familiar-with-the-sample-project&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These are the key files you will be working with in the sample project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;server.js&lt;/code&gt; contains the Node.js code that serves the web app&#39;s
content. It uses &lt;a href=&quot;https://expressjs.com/&quot; rel=&quot;noopener&quot;&gt;Express&lt;/a&gt; to handle HTTP requests
and responses. In particular, &lt;code&gt;express.static()&lt;/code&gt; is used to serve all of
the local files in the public directory, so the &lt;code&gt;serve-static&lt;/code&gt;
&lt;a href=&quot;https://expressjs.com/en/resources/middleware/serve-static.html&quot; rel=&quot;noopener&quot;&gt;documentation&lt;/a&gt;
will come in handy.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public/index.html&lt;/code&gt; is the web app&#39;s HTML. Like most HTML files, it does not
contain any versioning information as part of its URL.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public/app.15261a07.js&lt;/code&gt; and &lt;code&gt;public/style.391484cf.css&lt;/code&gt; are the web app&#39;s JavaScript
and CSS assets. These files each contain a hash in their URLs,
corresponding to their contents. The &lt;code&gt;index.html&lt;/code&gt; is responsible for keeping
track of which specific versioned URL to load.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; In the &amp;quot;real world&amp;quot;, the process of assigning hashes and updating HTML files to include references to the latest versioned URL would be handled by a build tool, like &lt;a href=&quot;https://webpack.js.org/guides/caching/#output-filenames&quot;&gt;webpack&lt;/a&gt;. For the purposes of this codelab, assume that the hashes were generated as part of a build process that already took place. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;configure-caching-headers-for-our-html&quot;&gt;Configure caching headers for our HTML &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-http-cache/#configure-caching-headers-for-our-html&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When responding to requests for URLs that don&#39;t contain versioning info, make
sure you add &lt;code&gt;Cache-Control: no-cache&lt;/code&gt; to your response messages. Along with
that, setting one of two additional response headers is recommended: either
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Last-Modified&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Last-Modified&lt;/code&gt;&lt;/a&gt;
or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ETag&lt;/code&gt;&lt;/a&gt;. The
&lt;code&gt;index.html&lt;/code&gt; falls into this category. You can break this down into two steps.&lt;/p&gt;
&lt;p&gt;First, the &lt;code&gt;Last-Modified&lt;/code&gt; and &lt;code&gt;ETag&lt;/code&gt; headers are controlled by the
&lt;a href=&quot;https://expressjs.com/en/resources/middleware/serve-static.html#etag&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;etag&lt;/code&gt;&lt;/a&gt;
and
&lt;a href=&quot;https://expressjs.com/en/resources/middleware/serve-static.html#lastmodified&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;lastModified&lt;/code&gt;&lt;/a&gt;
configuration options. Both of these options actually default to &lt;code&gt;true&lt;/code&gt; for all
HTTP responses, so in this current setup, you don&#39;t &lt;em&gt;have&lt;/em&gt; to opt-in to get that
behavior. But you can be explicit in your configuration anyway.&lt;/p&gt;
&lt;p&gt;Second, you need to be able to add in the &lt;code&gt;Cache-Control: no-cache&lt;/code&gt; header, but
only for your HTML documents (&lt;code&gt;index.html&lt;/code&gt;, in this case). The easiest way to
conditionally set this header is to write a custom
&lt;a href=&quot;https://expressjs.com/en/resources/middleware/serve-static.html#setheaders&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;setHeaders function&lt;/code&gt;&lt;/a&gt;,
and within that, check to see if the incoming request is for an HTML document.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Remix to Edit&lt;/strong&gt; to make the project editable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The static serving configuration in &lt;code&gt;server.js&lt;/code&gt; starts out as this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;express&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;public&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Make the changes described above, and you should end up with something that
looks like:&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;express&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;public&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;etag&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Just being explicit about the default.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;lastModified&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Just being explicit about the default.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function-variable function&quot;&gt;setHeaders&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;res&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// All of the project&#39;s HTML files end in .html&lt;/span&gt;&lt;br /&gt;      res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setHeader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Cache-Control&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;no-cache&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;configure-caching-headers-for-the-versioned-urls&quot;&gt;Configure caching headers for the versioned URLs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-http-cache/#configure-caching-headers-for-the-versioned-urls&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When responding to requests for URLs that contain
&amp;quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/Fingerprint_(computing)&quot; rel=&quot;noopener&quot;&gt;fingerprint&lt;/a&gt;&amp;quot; or
versioning information, and whose contents are never meant to change, add
&lt;code&gt;Cache-Control: max-age=31536000&lt;/code&gt; to your responses. The &lt;code&gt;app.15261a07.js&lt;/code&gt; and
&lt;code&gt;style.391484cf.css&lt;/code&gt; fall into this category.&lt;/p&gt;
&lt;p&gt;Building off the
&lt;a href=&quot;https://expressjs.com/en/resources/middleware/serve-static.html#setheaders&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;setHeaders function&lt;/code&gt;&lt;/a&gt;
used in the last step, you can add in additional logic to check whether a given
request is for a versioned URL, and if so, add the &lt;code&gt;Cache-Control: max-age=31536000&lt;/code&gt; header.&lt;/p&gt;
&lt;p&gt;The most robust way of doing this is to use a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions&quot; rel=&quot;noopener&quot;&gt;regular expression&lt;/a&gt;
to see whether the asset being requested matches a specific pattern that you
know the hashes fall into. In the case of this sample project, it&#39;s always eight
characters from the set of digits 0–9 and the lowercase letters a–f (i.e.
&lt;a href=&quot;https://en.wikipedia.org/wiki/Hexadecimal&quot; rel=&quot;noopener&quot;&gt;hexadecimal&lt;/a&gt; characters). The hash
is always separated by a &lt;code&gt;.&lt;/code&gt; character on either side.&lt;/p&gt;
&lt;p&gt;A regular expression that
&lt;a href=&quot;https://jex.im/regulex/#!flags=&amp;amp;re=%5C.%5B0-9a-f%5D%7B8%7D%5C.&quot; rel=&quot;noopener&quot;&gt;matches those general rules&lt;/a&gt;
can be expressed as &lt;code&gt;new RegExp(&#39;\\.[0-9a-f]{8}\\.&#39;)&lt;/code&gt;.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; It helps to be as specific as possible when coming up with these rules, to protect against future problems. A more general match, such as checking for the &lt;code&gt;.js&lt;/code&gt; or &lt;code&gt;.css&lt;/code&gt; file extension, could end up being a problem down the road if you end up adding in additional, unversioned JavaScript or CSS assets to your project. &lt;/div&gt;&lt;/aside&gt;
&lt;ul&gt;
&lt;li&gt;Modify the &lt;code&gt;setHeaders&lt;/code&gt; function so it looks like this:&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;express&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;public&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;etag&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Just being explicit about the default.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;lastModified&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Just being explicit about the default.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function-variable function&quot;&gt;setHeaders&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;res&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&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; hashRegExp &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;RegExp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;\\.[0-9a-f]{8}\\.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// All of the project&#39;s HTML files end in .html&lt;/span&gt;&lt;br /&gt;      res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setHeader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Cache-Control&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;no-cache&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hashRegExp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// If the RegExp matched, then we have a versioned URL.&lt;/span&gt;&lt;br /&gt;      res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setHeader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Cache-Control&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;max-age=31536000&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;confirm-the-new-behavior-using-devtools&quot;&gt;Confirm the new behavior using DevTools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-http-cache/#confirm-the-new-behavior-using-devtools&quot;&gt;#&lt;/a&gt;&lt;/h2&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 get familiar with the Network panel in Chrome&#39;s DevTools by working through &lt;a href=&quot;https://web.dev/codelab-explore-network-panel&quot;&gt;this codelab&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;With the modifications to the static file server in place, you can check to make
sure that the right headers are being set by previewing the live app with the DevTools Network panel open.&lt;/p&gt;
&lt;ul&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;Customize the columns that are
displayed in the Network panel to include the information that is most relevant, by right-clicking in
the column header:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;img alt=&quot;Configuring DevTools&amp;#x27; Network panel.&quot; class=&quot;screenshot&quot; decoding=&quot;async&quot; height=&quot;931&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/L98OANnCWWOU36dTwd8r.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Here, the columns to pay attention to are &lt;code&gt;Name&lt;/code&gt;, &lt;code&gt;Status&lt;/code&gt;, &lt;code&gt;Cache-Control&lt;/code&gt;,
&lt;code&gt;ETag&lt;/code&gt;, and &lt;code&gt;Last-Modified&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;With the DevTools open to the Network panel, refresh the page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After the page has loaded, you should see entries in the Network panel that look
like the following:&lt;/p&gt;
&lt;img alt=&quot;Network panel columns.&quot; class=&quot;screenshot&quot; decoding=&quot;async&quot; height=&quot;172&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PQbDCXUvXPlqThlPbLIp.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;The first row is for the HTML document that you navigated to. This is properly
served with &lt;code&gt;Cache-Control: no-cache&lt;/code&gt;. The HTTP response status for that request
is a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Status/304&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;304&lt;/code&gt;&lt;/a&gt;. This
means that the browser knew not to use the cached HTML immediately, but instead
made an HTTP request to the web server, using the &lt;code&gt;Last-Modified&lt;/code&gt; and &lt;code&gt;ETag&lt;/code&gt;
information to see if there was any update to the HTML that it already had in
its cache. The HTTP 304 response indicates that there is not updated HTML.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; &lt;code&gt;Cache-Control: no-cache&lt;/code&gt; doesn&#39;t mean &amp;quot;never used the cached copy&amp;quot;. It means &amp;quot;always check with the server first, and use the cached copy if there&#39;s a HTTP 304 response.&amp;quot; &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;The next two rows are for the versioned JavaScript and CSS assets. You should
see them served with &lt;code&gt;Cache-Control: max-age=31536000&lt;/code&gt;, and the HTTP status for
each is &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Status/200&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;200&lt;/code&gt;&lt;/a&gt;.
Because of the configuration used, there is no actual request being made to the
Node.js server, and clicking on the entry will show you additional detail,
including that the response came &amp;quot;(from disk cache)&amp;quot;.&lt;/p&gt;
&lt;img alt=&quot;A network response status of 200.&quot; class=&quot;screenshot&quot; decoding=&quot;async&quot; height=&quot;175&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bx1DtsiX7e9tdOSf1ogZ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;The actual values for the ETag and Last-Modified columns don&#39;t matter much. The
important thing is to confirm that they&#39;re being set.&lt;/p&gt;
&lt;h2 id=&quot;summing-things-up&quot;&gt;Summing things up &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-http-cache/#summing-things-up&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Having gone through the steps in this codelab, you&#39;re now familiar with how to
configure the HTTP response headers in a Node.js-based web server using Express,
for optimal use of the HTTP cache. You also have the steps you need to confirm
that the expected caching behavior is being used, via the Network panel in
Chrome&#39;s DevTools.&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Working with service workers</title>
    <link href="https://web.dev/codelab-service-workers/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/codelab-service-workers/</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;This codelab shows you how to register a service worker from within your web
application, and use the Chrome DevTools to observe its behavior. It also
covers some debugging techniques that you might find useful when dealing with
service workers.&lt;/p&gt;
&lt;h2 id=&quot;get-familiar-with-the-sample-project&quot;&gt;Get familiar with the sample project &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-service-workers/#get-familiar-with-the-sample-project&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The files in the sample project most relevant to this codelab are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;register-sw.js&lt;/code&gt; starts out empty, but it will contain the code used
to register the service worker. It&#39;s already being loaded via a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;
tag inside of the project&#39;s &lt;code&gt;index.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;service-worker.js&lt;/code&gt; is similarly empty. It&#39;s the file that will contain
the service worker for this project.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;add-in-the-service-worker-registration-code&quot;&gt;Add in the service worker registration code &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-service-workers/#add-in-the-service-worker-registration-code&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A service worker (even an empty one, like the current &lt;code&gt;service-worker.js&lt;/code&gt; file)
won&#39;t be used unless it&#39;s
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ServiceWorkerContainer/register&quot; rel=&quot;noopener&quot;&gt;registered&lt;/a&gt;
first. You can do this via a call to:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&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;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;/service-worker.js&#39;&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;inside your &lt;code&gt;register-sw.js&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Before you add that code, though, there are a couple of points to take into
account.&lt;/p&gt;
&lt;p&gt;First, not every browser
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Service_Worker_API#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;supports&lt;/a&gt;
service workers. This is particularly true for older versions of browsers that
don&#39;t automatically update. So it&#39;s a best practice to call
&lt;code&gt;navigator.serviceWorker.register()&lt;/code&gt; conditionally, after checking whether
&lt;code&gt;navigator.serviceWorker&lt;/code&gt; is supported.&lt;/p&gt;
&lt;p&gt;Second, when you register a service worker, the browser runs the code in your
&lt;code&gt;service-worker.js&lt;/code&gt; file, and may potentially start downloading URLs to populate
caches, depending on the code in your service worker&#39;s
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#install&quot;&gt;&lt;code&gt;install&lt;/code&gt;&lt;/a&gt;
and
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#activate&quot;&gt;&lt;code&gt;activate&lt;/code&gt;&lt;/a&gt;
event handlers.&lt;/p&gt;
&lt;p&gt;Running additional code and downloading assets can use up
valuable resources that your browser could otherwise use to display the current
web page. To help avoid this interference, it&#39;s a good practice to delay
registering a service worker until the browser has finished rendering the
current page. A convenient way of approximating this is to wait until the
&lt;code&gt;window.load&lt;/code&gt; event has been fired.&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; Waiting for &lt;code&gt;window.load&lt;/code&gt; is a one-size-fits-all solution. If you know more about how your web page loads resources, or if you&#39;re using a web app framework that supports its own &amp;quot;everything&#39;s ready&amp;quot; lifecycle event, you can tweak the timing accordingly. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Putting those two points together, add this general-purpose service worker
registration code to your &lt;code&gt;register-sw.js&lt;/code&gt; file:&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;serviceWorker&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    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;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;add-some-service-worker-logging-code&quot;&gt;Add some service worker logging code &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-service-workers/#add-some-service-worker-logging-code&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Your &lt;code&gt;service-worker.js&lt;/code&gt; file is where all the logic for your service worker
implementation would normally go. You&#39;d use a mix of the service worker
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/&quot;&gt;lifecycle events&lt;/a&gt;,
the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/CacheStorage&quot; rel=&quot;noopener&quot;&gt;Cache Storage API&lt;/a&gt;,
and knowledge about your web app&#39;s network traffic to create a perfectly crafted
service worker, ready to handle all of your web app&#39;s requests.&lt;/p&gt;
&lt;p&gt;But… that&#39;s all for learning later. At this stage, the focus is on observing
various service worker events, and getting comfortable using Chrome&#39;s DevTools
to debug the state of your service worker.&lt;/p&gt;
&lt;p&gt;To that end, add in the following code to &lt;code&gt;service-worker.js&lt;/code&gt;, which will log
messages to the DevTools console in response to various events (but not do much
else):&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;self&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;install&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Inside the install handler:&#39;&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;&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;self&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;activate&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Inside the activate handler:&#39;&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;&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;self&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;fetch&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Inside the fetch handler:&#39;&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;&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;h2 id=&quot;get-familiar-with-the-service-workers-panel-in-devtools&quot;&gt;Get familiar with the Service Workers panel in DevTools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-service-workers/#get-familiar-with-the-service-workers-panel-in-devtools&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that you&#39;ve got the code added to &lt;code&gt;register-sw.js&lt;/code&gt; and &lt;code&gt;service-worker.js&lt;/code&gt;
files, it&#39;s time to visit the Live version of your sample project, and observe
the service worker in action.&lt;/p&gt;
&lt;ul&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;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.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You should see something like the following log messages,
showing that the service worker has been installed and activated:&lt;/p&gt;
&lt;img alt=&quot;Shows service worker is installed and activated.&quot; class=&quot;screenshot&quot; decoding=&quot;async&quot; height=&quot;88&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qN2ZoovNYzp7wZHvqY9c.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Then visit the &lt;strong&gt;Applications&lt;/strong&gt; tab, and select the &lt;strong&gt;Service Workers&lt;/strong&gt; panel.
You should see something like the following:&lt;/p&gt;
&lt;img alt=&quot;Shows service worker details in service worker panel.&quot; class=&quot;screenshot&quot; decoding=&quot;async&quot; height=&quot;386&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2jTU0nCScsyvHqhb3ajQ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;This lets you know that there&#39;s a service worker with a source URL of
&lt;code&gt;service-worker.js&lt;/code&gt;, for the web app &lt;code&gt;solar-donkey.glitch.me&lt;/code&gt;, that&#39;s currently
activated and running. It also tells you that there&#39;s currently one client (open
tab) that&#39;s being controlled by the service worker.&lt;/p&gt;
&lt;p&gt;You can use the links on this panel, like &lt;code&gt;Unregister&lt;/code&gt;, or &lt;code&gt;stop&lt;/code&gt;, to make
changes to the currently registered service worker for debugging purposes.&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; Service workers tend to stick around, unless they&#39;re explicitly unregistered. If you find yourself wanting to &amp;quot;start fresh&amp;quot; during local development, a great way of doing so is to use a &lt;a href=&quot;https://support.google.com/chrome/answer/95464&quot;&gt;Chrome Incognito window&lt;/a&gt; to load pages that are under service worker control. The service worker will persist only as long as the window is open, and you could always start over by closing all Incognito windows and opening a new one. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;trigger-the-service-worker-update-flow&quot;&gt;Trigger the service worker update flow &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-service-workers/#trigger-the-service-worker-update-flow&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the key concepts to understand when developing with service workers is
the idea of
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#updates&quot;&gt;an update flow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After your users visit a web app which registers a service worker, they&#39;ll end
up with the code for the current copy of &lt;code&gt;service-worker.js&lt;/code&gt; installed on their
local browser. But what happens when you make updates to the version of
service-worker.js that&#39;s stored on your web server?&lt;/p&gt;
&lt;p&gt;When a repeat visitor returns to a URL that&#39;s within the scope of a service worker,
the browser will automatically request the latest &lt;code&gt;service-worker.js&lt;/code&gt; and
check for any changes. If anything in the service worker script is different,
then the new service worker will get a chance to install, activate,
and eventually take control.&lt;/p&gt;
&lt;p&gt;You can simulate this update flow by going back to the code editor for your project, and making &lt;em&gt;any&lt;/em&gt; change to the code. One quick change would be
to replace&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;self&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;install&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Inside the install handler:&#39;&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;&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;with&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;self&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;install&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Inside the UPDATED install handler:&#39;&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;&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;After making that change, return to the Live version of your sample app, and
reload the page with the DevTools Application tab still open. You should see
something like the following:&lt;/p&gt;
&lt;img alt=&quot;Shows two versions of service worker installed.&quot; class=&quot;screenshot&quot; decoding=&quot;async&quot; height=&quot;454&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5zmK7uePWyZ4JMxXAEYd.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;This shows that there are two versions of your service worker installed at this
point. The previous version, which was already activated, is running and in
control of the current page. The updated version of the service worker is listed
right below. It&#39;s in the
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#waiting&quot;&gt;&lt;code&gt;waiting&lt;/code&gt; state&lt;/a&gt;,
and will remain waiting until all of the open tabs that are controlled by the
old service worker are closed.&lt;/p&gt;
&lt;p&gt;This default behavior ensures that if your new
service worker has a fundamental difference in behavior from your old one—like a
&lt;code&gt;fetch&lt;/code&gt; handler which responds with resources that are incompatible with older
versions of your web app—it won&#39;t go into effect until a user has shut down all
previous instances of your web app.&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; If you don&#39;t want to wait, it&#39;s possible to &lt;a href=&quot;https://web.dev/service-worker-lifecycle/#skip-the-waiting-phase&quot;&gt;call &lt;code&gt;skipWaiting()&lt;/code&gt;&lt;/a&gt; inside of your service worker (usually in the &lt;code&gt;install&lt;/code&gt; handler), or to simulate that behave by clicking on the &lt;code&gt;skipWaiting&lt;/code&gt; link in DevTools. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;summing-things-up&quot;&gt;Summing things up &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/codelab-service-workers/#summing-things-up&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You should now be comfortable with the process of registering a service worker
and observing a service worker&#39;s behavior using Chrome&#39;s DevTools.&lt;/p&gt;
&lt;p&gt;You&#39;re now in a good position to start implementing caching strategies, and all
the good stuff that will help your web app load both reliably and reliably
fast.&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Identify resources loaded from the network</title>
    <link href="https://web.dev/identify-resources-via-network-panel/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/identify-resources-via-network-panel/</id>
    <content type="html" mode="escaped">&lt;p&gt;The Network panel in your browser&#39;s DevTools helps identify what resources are
loaded and when they are loaded. Each row in the Network panel corresponds to a
specific URL that your web app has loaded.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Chrome DevTools&amp;#x27; network panel.&quot; decoding=&quot;async&quot; height=&quot;602&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7TVH0ZV5TBIe5qwNDzAn.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-quaternary-box-bg color-quaternary-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; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Code brackets&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;M9.41 16.59L8 18l-6-6 6-6 1.41 1.41L4.83 12l4.58 4.59zm5.18-9.18L16 6l6 6-6 6-1.41-1.41L19.17 12l-4.58-4.59z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Try it&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;a href=&quot;https://web.dev/codelab-explore-network-panel&quot;&gt;Interpret network traffic using Chrome&#39;s DevTools&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This guide uses screenshots and descriptions based on Chrome&#39;s DevTools interface. Other browsers support similar functionality, but the overall interface will be different if you&#39;re not using Chrome. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;know-what-you-load&quot;&gt;Know what you load &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/identify-resources-via-network-panel/#know-what-you-load&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Coming up with the right caching strategies for your web application requires
getting a handle on &lt;em&gt;what&lt;/em&gt; exactly you&#39;re loading. When building a reliable web
application, the network can be subject to all kinds of dark forces. You need to
understand the network&#39;s vulnerabilities if you hope to cope with them in your
app.&lt;/p&gt;
&lt;p&gt;You might think that you already have a pretty good idea as to what your web
application loads. If you&#39;re just using a small scattering of static HTML,
JavaScript, CSS, and image files, that might be true. But as soon as you start
mixing in third-party resources hosted on content delivery networks, using
dynamic API requests and server-generated responses, the picture quickly becomes
murkier.&lt;/p&gt;
&lt;p&gt;A caching strategy that makes sense for a few small CSS files probably won&#39;t
make sense for hundreds of large images, for instance.&lt;/p&gt;
&lt;h2 id=&quot;know-when-you-load&quot;&gt;Know when you load &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/identify-resources-via-network-panel/#know-when-you-load&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another part of the overall loading picture is &lt;em&gt;when&lt;/em&gt; everything gets loaded.&lt;/p&gt;
&lt;p&gt;Some requests to the network, such as the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Request/mode#Value&quot; rel=&quot;noopener&quot;&gt;navigation request&lt;/a&gt;
for your initial HTML, are made unconditionally as soon as a user visits a given
URL. That HTML might contain hardcoded references to critical CSS or JavaScript
files that must also load in order to display your interactive page. These
requests all sit in your critical loading path. You will need to aggressively
cache these to be reliably fast.&lt;/p&gt;
&lt;p&gt;Other resources, such as API requests or lazy-loaded assets, might not
start to load until well after all of the initial loading is complete. If
those requests only happen following a specific sequence of user interactions,
then a completely different set of resources might end up being requested
across multiple visits to the same page. A less aggressive caching strategy is
often appropriate for content that you&#39;ve identified as being outside the
critical loading path.&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; Advanced techniques outside the scope of this guide, like &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Preloading_content&quot;&gt;&lt;link rel=&quot;preload&quot; /&gt;&lt;/a&gt;, add a twist to this story by giving a head start to what would otherwise be a late-loaded request. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;the-name-and-type-columns-help-with-the-what&quot;&gt;The Name and Type columns help with the what &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/identify-resources-via-network-panel/#the-name-and-type-columns-help-with-the-what&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Name and Type columns help provide a meaningful picture of &lt;em&gt;what&lt;/em&gt;&#39;s being
loaded. The answer to &amp;quot;&lt;em&gt;what&lt;/em&gt;&#39;s loading?&amp;quot; in the example above is a total of
four URLs, each representing a unique type of content.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Chrome DevTools&amp;#x27; network panel showing four files loading.&quot; decoding=&quot;async&quot; height=&quot;602&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9wqFFPDtX9BPLvrVOQhc.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The Name represents the URL that your browser requested—though you&#39;ll only see
the last portion of the URL&#39;s path listed. For example, if
&lt;code&gt;https://example.com/main.css&lt;/code&gt; is loaded, you&#39;d only end up seeing &lt;code&gt;main.css&lt;/code&gt;
listed under Name.&lt;/p&gt;
&lt;p&gt;The last few characters of the URL&#39;s path, following the
period (e.g. &amp;quot;css&amp;quot;), are known as the URL&#39;s extension.
The URL&#39;s extension generally tells you what type of resource is being requested,
and maps directly to the information shown in the Type column. For example,
&lt;code&gt;v2.html&lt;/code&gt; is a document, and &lt;code&gt;main.css&lt;/code&gt; is a stylesheet.&lt;/p&gt;
&lt;h3 id=&quot;the-waterfall-column-helps-with-the-when&quot;&gt;The Waterfall column helps with the when &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/identify-resources-via-network-panel/#the-waterfall-column-helps-with-the-when&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Examine the Waterfall column, starting at the top and working your way down. The
length of each bar represents the total amount of time that was spent loading
each resource. How can you tell the difference between a request that&#39;s made as
part of the critical loading path and a request that&#39;s fired off dynamically,
long after the page&#39;s initial load is complete?&lt;/p&gt;
&lt;p&gt;The first request in the waterfall is always going to be for the HTML document,
for example, &lt;code&gt;v2.html&lt;/code&gt;. All of the subsequent requests will flow (like a
waterfall!) from this initial navigation request, based on what images, scripts,
and styles the HTML document references.&lt;/p&gt;
&lt;img alt=&quot;Chrome DevTools&amp;#x27; waterfall view.&quot; decoding=&quot;async&quot; height=&quot;244&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 774px) 774px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuPs0Rk4nWlSP74FOJ5Q.png?auto=format&amp;w=1548 1548w&quot; width=&quot;774&quot; /&gt;
&lt;p&gt;The waterfall shows that as soon as &lt;code&gt;v2.html&lt;/code&gt; has finished loading, the requests
for the assets it references (also referred to as &lt;em&gt;subresources&lt;/em&gt;) start. The
browser can request multiple subresources at the same time, and that&#39;s
represented by the overlapping bars in the Waterfall column for &lt;code&gt;main.css&lt;/code&gt; and
&lt;code&gt;logo.svg&lt;/code&gt;. Finally, you can see from the screenshot that &lt;code&gt;main.js&lt;/code&gt; starts
loading last, and it finishes loading after the other three URLs have completed
as well.&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Precaching with Workbox</title>
    <link href="https://web.dev/precache-with-workbox/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/precache-with-workbox/</id>
    <content type="html" mode="escaped">&lt;p&gt;One feature of service workers is the ability to save files to the cache when
the service worker is installing. This is referred to as &amp;quot;precaching&amp;quot;.
Precaching makes it possible to serve cached files to the browser without going
to the network. Use precaching for critical assets that your site needs even
when offline: main page, styles, fallback image and essential scripts.&lt;/p&gt;
&lt;h2 id=&quot;why-should-you-use-workbox&quot;&gt;Why should you use Workbox? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/precache-with-workbox/#why-should-you-use-workbox&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using Workbox for precaching is optional. You can write your own code to
&lt;a href=&quot;https://web.dev/learn/pwa/caching/&quot;&gt;precache critical assets when the service worker is installing&lt;/a&gt;.
The primary benefit of using Workbox is its out-of-the-box version control.
You&#39;ll run into a lot less trouble updating precached assets using Workbox than
if you had to manage the versioning and updating of these files on your own.&lt;/p&gt;
&lt;h2 id=&quot;precache-manifests&quot;&gt;Precache manifests &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/precache-with-workbox/#precache-manifests&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Precaching is driven by a list of URLs and associated versioning information for
each URL. Taken together, this information is known as a
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-precaching/#explanation-of-the-precache-list&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;precache manifest&lt;/strong&gt;&lt;/a&gt;.
The manifest is the &amp;quot;source of truth&amp;quot; for the state of everything meant to be in
the precache for a given version of a web app. A precache manifest, in the
format used by &lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt;,
looks something like:&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 punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;app.abcd1234.js&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;offline.svg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;7836745f&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;index.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;518747aa&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;When the service worker is installed, three cache entries are created in the
Cache Storage, for each of the three listed URLs. The first asset has versioning
information already included in its URL (&lt;code&gt;app&lt;/code&gt; is the actual file name, and
&lt;code&gt;.abcd1234&lt;/code&gt; contains the versioning information, right before the file extension
&lt;code&gt;.js&lt;/code&gt;). Workbox&#39;s build tools can detect this and exclude a revision field. The
other two assets do not include any versioning info in their URLs, so Workbox&#39;s
build tools create a separate &lt;code&gt;revision&lt;/code&gt; field, containing a hash of the local
file&#39;s contents.&lt;/p&gt;
&lt;h2 id=&quot;serving-precached-resources&quot;&gt;Serving precached resources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/precache-with-workbox/#serving-precached-resources&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Adding assets to the cache is just one part of the precaching story—once the
assets are cached, they need to respond to outgoing requests. That requires a
&lt;code&gt;fetch&lt;/code&gt; event listener in your service worker that can check which URLs have
been precached, and return those cached responses reliably, bypassing the
network in the process. Since the service worker checks the cache for responses
(and uses those before the network), this is called a
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#cache-first-cache-falling-back-to-network&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;cache-first strategy&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;efficient-updates&quot;&gt;Efficient updates &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/precache-with-workbox/#efficient-updates&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A precache manifest provides an exact representation of the expected cache
state; if a URL/revision combination in the manifest changes, a service worker
&lt;em&gt;knows&lt;/em&gt; that the previous cached entry is no longer valid, and needs to be
updated. It only takes a single network request, made automatically by the
browser as part of the service worker
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#updates&quot;&gt;update check&lt;/a&gt;,
to determine which precached URLs need to be refreshed.&lt;/p&gt;
&lt;h2 id=&quot;updates-to-precached-resources&quot;&gt;Updates to precached resources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/precache-with-workbox/#updates-to-precached-resources&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As you release new versions of your web app over time, you need to keep
previously precached URLs up to date, precache new assets, and delete outdated
entries. As long as you continue generating a full precache manifest each time
you rebuild your site, that manifest serves as the &amp;quot;source of truth&amp;quot; for your
precache state at any point in time.&lt;/p&gt;
&lt;p&gt;If there&#39;s an existing URL with a new revision field, or if any URLs have been
added or dropped from the manifest, that&#39;s a sign to your service worker that
updates need to be performed, as part of the
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#install&quot;&gt;&lt;code&gt;install&lt;/code&gt;&lt;/a&gt;
and
&lt;a href=&quot;https://web.dev/service-worker-lifecycle/#activate&quot;&gt;&lt;code&gt;activate&lt;/code&gt;&lt;/a&gt;
event handlers. For instance, if you&#39;ve made some changes to your site and
rebuilt, your latest precache manifest might have undergone the following
changes:&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 punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;app.ffaa4455.js&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;offline.svg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;7836745f&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;index.html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;518747aa&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Each of these changes tells your service worker that new requests need to be
made to update previously cached entries (&lt;code&gt;&#39;offline.svg&#39;&lt;/code&gt; and &lt;code&gt;&#39;index.html&#39;&lt;/code&gt;)
and cache new URLs (&lt;code&gt;&#39;app.ffaa4455.js&#39;&lt;/code&gt;), as well as deletions to clean up URLs
that are no longer used (&lt;code&gt;&#39;app.abcd1234.js&#39;&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id=&quot;true-app-store-install-experience&quot;&gt;True &amp;quot;app store&amp;quot; install experience &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/precache-with-workbox/#true-app-store-install-experience&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another benefit of precaching is that you can give your users an experience that
would otherwise be difficult to achieve outside of an &amp;quot;app store&amp;quot;
installation. Once a user visits any page on your web app for the first time,
you can potentially precache &lt;em&gt;all&lt;/em&gt; of the URLs needed to display &lt;em&gt;any&lt;/em&gt; of your
web app&#39;s views ahead of time, without having to wait until they visit each
individual view.&lt;/p&gt;
&lt;p&gt;When a user installs an app, they expect every part to display quickly,
not just the views that they&#39;ve gone to in the past. Precaching brings that same
experience to web apps.&lt;/p&gt;
&lt;h2 id=&quot;which-of-your-assets-should-be-precached&quot;&gt;Which of your assets should be precached? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/precache-with-workbox/#which-of-your-assets-should-be-precached&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Refer back to the &lt;a href=&quot;https://web.dev/identify-resources-via-network-panel/&quot;&gt;Identify what&#39;s being
loaded&lt;/a&gt; guide to get a good
picture of which URLs make the most sense to precache. The general rule is to
precache any HTML, JavaScript, or CSS that&#39;s loaded early on and is crucial to
displaying the basic structure of a given page.&lt;/p&gt;
&lt;p&gt;It&#39;s preferable to avoid precaching media or other assets that are loaded later
(unless crucial for your web app&#39;s functionality). Instead, use a &lt;a href=&quot;https://web.dev/runtime-caching-with-workbox/&quot;&gt;runtime
caching strategy&lt;/a&gt; to ensure these
assets are cached-as-you-go.&lt;/p&gt;
&lt;p&gt;Always keep in mind that precaching involves using network bandwidth and storage
on a user&#39;s device (just like installing an app from an app store does).
It&#39;s up to you as the developer to precache judiciously, and avoid a bloated
precache manifest.&lt;/p&gt;
&lt;p&gt;Workbox&#39;s build tools help by telling you the number of items in the precache
manifest as well as the total size of the precache payload.&lt;/p&gt;
&lt;p&gt;Repeat visitors to your web app benefit in the long run from the upfront cost of
precaching, since the ability it offers to avoid the network quickly pays for
itself in saved bandwidth over time.&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Runtime caching with Workbox</title>
    <link href="https://web.dev/runtime-caching-with-workbox/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/runtime-caching-with-workbox/</id>
    <content type="html" mode="escaped">&lt;p&gt;Runtime caching refers to gradually adding responses to a cache &amp;quot;as you go&amp;quot;.
While runtime caching doesn&#39;t help with the reliability of the &lt;em&gt;current&lt;/em&gt;
request, it can help make &lt;em&gt;future&lt;/em&gt; requests for the same URL more reliable.&lt;/p&gt;
&lt;p&gt;The browser&#39;s HTTP cache is an example of runtime caching; it&#39;s only populated
after a request for a given URL. But service workers allow you to implement
runtime caching that goes beyond what the HTTP cache alone can offer.&lt;/p&gt;
&lt;h2 id=&quot;getting-strategic&quot;&gt;Getting strategic &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/runtime-caching-with-workbox/#getting-strategic&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As opposed to &lt;a href=&quot;https://web.dev/precache-with-workbox/&quot;&gt;precaching&lt;/a&gt; (which always tries
to serve a set of predefined files from a cache), runtime caching can combine
network and cache access in multiple ways. Each combination is generally
referred to as a caching strategy. Key caching strategies include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Network-first&lt;/li&gt;
&lt;li&gt;Cache-first&lt;/li&gt;
&lt;li&gt;Stale-while-revalidate&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;network-first&quot;&gt;Network-first &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/runtime-caching-with-workbox/#network-first&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this approach, your service worker first attempts to retrieve a response from
the network. If the network request succeeds, great! The response is returned to
your web app, and a copy of the response is stored using the Cache Storage
API—either creating a new entry, or updating a previous entry for the same
URL.&lt;/p&gt;
&lt;img alt=&quot;Diagram showing the request going from the page to the service worker and from the service worker to the network. The network request fails so the request goes to the cache.&quot; decoding=&quot;async&quot; height=&quot;388&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/AyTKqrG1aBH2JOkz3LzL.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;If the network request fails entirely, or
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/forcing-a-network-timeout/&quot; rel=&quot;noopener&quot;&gt;takes too long&lt;/a&gt;
to return a response, then the most recent response from the cache is returned
instead.&lt;/p&gt;
&lt;h3 id=&quot;cache-first&quot;&gt;Cache-first &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/runtime-caching-with-workbox/#cache-first&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A cache-first strategy is effectively the opposite of network-first. In this
approach, when your service worker intercepts a request, it first uses the Cache
Storage API to see whether there&#39;s a cached response available. If there is,
that response is returned to the web app.&lt;/p&gt;
&lt;p&gt;If there&#39;s a cache miss, though, then the service worker will go to the network
and attempt to retrieve a response there. Assuming that network request is
successful, it&#39;s returned to your web app and a copy is saved in a cache. This
cached copy will be used to bypass the network the next time a request for the
same URLs is made.&lt;/p&gt;
&lt;img alt=&quot;Diagram showing the request going from the page to the service worker and from the service worker to the cache. The cache request fails so the request goes to the network.&quot; decoding=&quot;async&quot; height=&quot;395&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/HR4BhK1uWqjve9bC5h6u.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;stale-while-revalidate&quot;&gt;Stale-while-revalidate &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/runtime-caching-with-workbox/#stale-while-revalidate&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Stale-while-revalidate is something of a hybrid. When using it, your service
worker will immediately check for a cached response and, if one is found, pass
it back to your web app.&lt;/p&gt;
&lt;p&gt;In the meantime, regardless of whether there was a cache match, your service
worker also fires off a network request to get back a &amp;quot;fresh&amp;quot; response. This
response is used to update any previously cached response. If the initial cache
check was a miss, a copy of the network response is also passed back to your web
app.&lt;/p&gt;
&lt;img alt=&quot;Diagram showing the request going from the page to the service worker and from the service worker to the cache. The cache immediately returns a response while also fetching an update from the network for future requests.&quot; decoding=&quot;async&quot; height=&quot;388&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/lPpEfVFxdd9qGqLIx1gy.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;why-should-you-use-workbox&quot;&gt;Why should you use Workbox? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/runtime-caching-with-workbox/#why-should-you-use-workbox&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;These caching strategies amount to recipes that you would normally have to
rewrite in your own service worker, again and again. Instead of resorting to
that, Workbox offers them packaged up as part of its
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-strategies/&quot; rel=&quot;noopener&quot;&gt;strategies library&lt;/a&gt;,
ready for you to drop in to your service worker.&lt;/p&gt;
&lt;p&gt;Workbox also provides versioning support, allowing you to automatically
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-expiration/&quot; rel=&quot;noopener&quot;&gt;expire&lt;/a&gt;
cached entries, or notify your web app when
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-broadcast-update/&quot; rel=&quot;noopener&quot;&gt;updates&lt;/a&gt;
to a previously cached entry occur.&lt;/p&gt;
&lt;h2 id=&quot;which-of-your-assets-should-be-cached,-with-which-strategies&quot;&gt;Which of your assets should be cached, with which strategies? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/runtime-caching-with-workbox/#which-of-your-assets-should-be-cached,-with-which-strategies&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Runtime caching can be viewed as a complement to precaching. If all of your
assets are already being precached, then you&#39;re done—there&#39;s nothing that needs
to be cached at runtime. Chances are, for any relatively complex web app, you&#39;re
not going to be precaching &lt;em&gt;everything&lt;/em&gt; though.&lt;/p&gt;
&lt;p&gt;Larger media files, assets that are served from a third-party host like a CDN,
or API responses, are just a few examples of the types of assets that can&#39;t be
effectively precached. Use the Network panel in DevTools to identify requests
that fall into this category, and for each of them, think about what tradeoff of
freshness vs. reliability is appropriate.&lt;/p&gt;
&lt;h3 id=&quot;use-stale-while-revalidate-to-prioritize-reliability-over-freshness&quot;&gt;Use stale-while-revalidate to prioritize reliability over freshness &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/runtime-caching-with-workbox/#use-stale-while-revalidate-to-prioritize-reliability-over-freshness&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Since a stale-while-revalidate strategy returns a cached response almost
immediately—after the cache has been populated via the first request—you&#39;ll end
up seeing reliably fast performance when using this strategy. This comes with
the tradeoff of getting back response data that could be stale in comparison to
what would have been retrieved from the network. Using this strategy works best
for assets like user profile images or the initial API responses used to
populate a view, when you know that showing something &lt;em&gt;immediately&lt;/em&gt; is key, even
if it&#39;s an older value.&lt;/p&gt;
&lt;h3 id=&quot;use-network-first-to-prioritize-freshness-over-reliability&quot;&gt;Use network-first to prioritize freshness over reliability &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/runtime-caching-with-workbox/#use-network-first-to-prioritize-freshness-over-reliability&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In some sense, using a network-first strategy is admitting defeat in your battle
against the network—it&#39;s given priority, but that brings with it uncertainty
about reliability. For certain types of assets, seeing a fresh response is
preferable to getting back stale information. You might prefer freshness when
making an API request for the text of an article that is updated frequently, for
instance.&lt;/p&gt;
&lt;p&gt;By using a network-first strategy inside of a service worker, instead of just
going against the network directly, you have the benefit of being able to fall
back to &lt;em&gt;something&lt;/em&gt;, even if it&#39;s a potentially stale response. You won&#39;t be
reliably fast, but at least you&#39;ll be reliable while offline.&lt;/p&gt;
&lt;h3 id=&quot;use-cache-first-for-versioned-urls&quot;&gt;Use cache-first for versioned URLs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/runtime-caching-with-workbox/#use-cache-first-for-versioned-urls&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In a cache-first strategy, once an entry is cached, it&#39;s never updated. For that
reason, make sure that you only use it with assets that you know are unlikely to
change. In particular, it works best for URLs that contain versioning
information—the same sort of URLs that should also be served with a
&lt;code&gt;Cache-Control: max-age=31536000&lt;/code&gt; response header.&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Workbox: your high-level service worker toolkit</title>
    <link href="https://web.dev/workbox/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/workbox/</id>
    <content type="html" mode="escaped">&lt;p&gt;Two APIs play a crucial role in building reliable web apps:
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Service_Worker_API&quot; rel=&quot;noopener&quot;&gt;Service Worker&lt;/a&gt;
and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Cache&quot; rel=&quot;noopener&quot;&gt;Cache Storage&lt;/a&gt;. But
using them effectively—without introducing subtle bugs or bumping into edge
cases—can be a challenge. For example, errors in your service worker code can
cause caching problems; users might be shown out-of-date content or broken
links.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt; is a high-level
service worker toolkit built on top of the Service Worker and Cache Storage
APIs. It provides a production-ready set of libraries for adding offline support to
web apps. The toolkit is structured into two collections: tools that help manage
code that runs inside of your service worker, and tools that integrate with your
build process.&lt;/p&gt;
&lt;h3 id=&quot;runtime-code&quot;&gt;Runtime code &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox/#runtime-code&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is the code that runs inside of your service worker script and controls how
it intercepts outgoing requests and interacts with the Cache Storage API.
Workbox has a
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/&quot; rel=&quot;noopener&quot;&gt;dozen or so library modules in total&lt;/a&gt;,
that each handle a variety of specialized use cases. The most important modules
determine &lt;em&gt;whether&lt;/em&gt; the service worker will respond (known as
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-routing/&quot; rel=&quot;noopener&quot;&gt;routing&lt;/a&gt;),
and &lt;em&gt;how&lt;/em&gt; it will respond (known as the
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-strategies/&quot; rel=&quot;noopener&quot;&gt;caching strategy&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&quot;build-integration&quot;&gt;Build integration &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox/#build-integration&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Workbox offers
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-cli/&quot; rel=&quot;noopener&quot;&gt;command line&lt;/a&gt;,
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-build/&quot; rel=&quot;noopener&quot;&gt;Node.js module&lt;/a&gt;,
and
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/&quot; rel=&quot;noopener&quot;&gt;webpack plugin&lt;/a&gt;
tools that provide alternative ways to accomplish two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a service worker script based on a set of configuration
options. The generated service worker uses Workbox&#39;s runtime libraries
&amp;quot;under the hood&amp;quot; to put into action the caching strategies you configure.&lt;/li&gt;
&lt;li&gt;Generate a list of URLs that should be
&amp;quot;&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-precaching/&quot; rel=&quot;noopener&quot;&gt;precached&lt;/a&gt;&amp;quot;,
based on configurable patterns to include and exclude files generated
during your build process.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-should-you-use-workbox&quot;&gt;Why should you use Workbox? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox/#why-should-you-use-workbox&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using Workbox when building your service worker is optional—there are a number
of guides out there that walk through
&lt;a href=&quot;https://web.dev/offline-cookbook/&quot;&gt;common caching strategies&lt;/a&gt;
from a &amp;quot;vanilla&amp;quot; service worker perspective. If you do decide to use Workbox,
here are some of its benefits.&lt;/p&gt;
&lt;h3 id=&quot;cache-management&quot;&gt;Cache management &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox/#cache-management&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Workbox handles cache updates for you, either tied in to your build process when
using precaching, or via configurable size/age policies when using runtime
caching. The underlying Cache Storage API is powerful, but it does not have any
built-in support for cache expiration. Tools like Workbox fill that gap.&lt;/p&gt;
&lt;h3 id=&quot;extensive-logging-and-error-reporting&quot;&gt;Extensive logging and error reporting &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox/#extensive-logging-and-error-reporting&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When you&#39;re getting started with service workers, figuring out &lt;em&gt;why&lt;/em&gt; something
is being cached (or, equally frustrating, why it &lt;em&gt;isn&#39;t&lt;/em&gt; cached) is a challenge.
Workbox automatically detects when you&#39;re running a development version of your
website on &lt;code&gt;localhost&lt;/code&gt;, and turns on debug logging in your browser&#39;s JavaScript
console.&lt;/p&gt;
&lt;img alt=&quot;Workbox logging to the DevTools console&quot; decoding=&quot;async&quot; height=&quot;438&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gvsGSU3urfjl52jRcj3j.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;By following along with the log messages, you can get to the root of any
configuration or invalidation problems much more quickly than if you were going
it alone.&lt;/p&gt;
&lt;h3 id=&quot;a-tested,-cross-browser-codebase&quot;&gt;A tested, cross-browser codebase &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox/#a-tested,-cross-browser-codebase&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Workbox is developed against a cross-browser test suite, and when possible,
automatically falls back to alternative implementations of features that are
missing from certain browsers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-broadcast-update/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;workbox-broadcast-cache-update module&lt;/code&gt;&lt;/a&gt;
uses the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Broadcast_Channel_API&quot; rel=&quot;noopener&quot;&gt;Broadcast Channel API&lt;/a&gt;
when available, and falls back to a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/postMessage&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;postMessage()&lt;/code&gt;&lt;/a&gt;-based
implementation on browsers that lack support.&lt;/li&gt;
&lt;li&gt;The
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-background-sync/&quot; rel=&quot;noopener&quot;&gt;workbox-background-sync module&lt;/a&gt;
uses the
&lt;a href=&quot;https://developer.chrome.com/blog/background-sync/&quot; rel=&quot;noopener&quot;&gt;Background Sync API&lt;/a&gt;
if possible, and if not, falls back to retrying queued events each time the
service worker starts up.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-should-you-use-workbox&quot;&gt;How should you use Workbox? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox/#how-should-you-use-workbox&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;framework-integration&quot;&gt;Framework integration &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox/#framework-integration&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;re starting a new project from scratch, you can take advantage of the
Workbox integration found in many popular starter kits and add-on plugins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app&quot; rel=&quot;noopener&quot;&gt;create-react-app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-plugin-pwa/README.md&quot; rel=&quot;noopener&quot;&gt;vue-cli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/prateekbh/preact-cli-workbox-plugin/blob/master/README.md&quot; rel=&quot;noopener&quot;&gt;preact-cli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gatsbyjs.org/packages/gatsby-plugin-offline/&quot; rel=&quot;noopener&quot;&gt;Gatsby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hanford/next-offline/blob/master/readme.md&quot; rel=&quot;noopener&quot;&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;add-workbox-to-your-existing-build-process&quot;&gt;Add Workbox to your existing build process &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox/#add-workbox-to-your-existing-build-process&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you already have a build process for your site in place, dropping in the
appropriate
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-cli/&quot; rel=&quot;noopener&quot;&gt;command line&lt;/a&gt;,
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-build/&quot; rel=&quot;noopener&quot;&gt;Node.js module&lt;/a&gt;,
or
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/&quot; rel=&quot;noopener&quot;&gt;webpack plugin&lt;/a&gt;
tool may be all you need to start using Workbox.&lt;/p&gt;
&lt;p&gt;In particular, the Workbox command line interface makes it easy to get up and
running, featuring a &lt;code&gt;wizard&lt;/code&gt; mode that will check your local development
environment and suggest a reasonable default configuration that you could use
moving forward:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;workbox wizard&lt;br /&gt;? What is the root of your web app &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i.e. &lt;span class=&quot;token function&quot;&gt;which&lt;/span&gt; directory &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; you deploy&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;? src/&lt;br /&gt;? Which &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; types would you like to precache? css, js, html&lt;br /&gt;? Where would you like your &lt;span class=&quot;token function&quot;&gt;service&lt;/span&gt; worker &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; to be saved? build/sw.js&lt;br /&gt;? Where would you like to save these configuration options? workbox-config.js&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;To build your service worker, run &lt;code&gt;workbox generateSW workbox-config.js&lt;/code&gt;
as part of a build process. See the &lt;a href=&quot;https://goo.gl/fdTQBf&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;generateSW&lt;/code&gt; documentation&lt;/a&gt; for details.
You can further customize your service worker by making changes to &lt;code&gt;workbox-config.js&lt;/code&gt;.
See the &lt;a href=&quot;https://goo.gl/gVo87N&quot; rel=&quot;noopener&quot;&gt;documentation of the options&lt;/a&gt; for details.&lt;/p&gt;
&lt;h3 id=&quot;use-workbox-at-runtime-in-an-existing-service-worker&quot;&gt;Use Workbox at runtime in an existing service worker &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/workbox/#use-workbox-at-runtime-in-an-existing-service-worker&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you have an existing service worker and want to try out Workbox&#39;s runtime
libraries,
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/modules/workbox-sw/#using-workbox-sw-via-cdn&quot; rel=&quot;noopener&quot;&gt;import Workbox from its official CDN&lt;/a&gt;
and start using it for runtime caching right away. This use
case means that you won&#39;t be able to take advantage of precaching (which
requires build-time integration), but it&#39;s great for prototyping and trying out
different caching strategies on the fly.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Replace 3.6.3 with the current version number of Workbox.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;importScripts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.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;br /&gt;&lt;br /&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RegExp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;\.png$&#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;  workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cacheFirst&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;images-cache&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;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;</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Service worker registration</title>
    <link href="https://web.dev/service-workers-registration/"/>
    <updated>2016-11-28T00:00:00Z</updated>
    <id>https://web.dev/service-workers-registration/</id>
    <content type="html" mode="escaped">&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/docs/workbox/service-worker-overview/&quot; rel=&quot;noopener&quot;&gt;Service
workers&lt;/a&gt;
can meaningfully speed up repeat visits to your web app, but you should take
steps to ensure that a service worker&#39;s initial installation doesn&#39;t degrade a
user&#39;s first-visit experience.&lt;/p&gt;
&lt;p&gt;Generally, deferring service worker
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ServiceWorkerContainer/register&quot; rel=&quot;noopener&quot;&gt;registration&lt;/a&gt;
until after the initial page has loaded will provide the best experience for
users, especially those on mobile devices with slower network connections.&lt;/p&gt;
&lt;h2 id=&quot;common-registration-boilerplate&quot;&gt;Common registration boilerplate &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-registration/#common-registration-boilerplate&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;ve ever read about service workers, you&#39;ve probably come across
boilerplate substantially similar to the following:&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;serviceWorker&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    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;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This might sometimes be accompanied by a few &lt;code&gt;console.log()&lt;/code&gt; statements, or
&lt;a href=&quot;https://github.com/GoogleChrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js#L20&quot; rel=&quot;noopener&quot;&gt;code&lt;/a&gt;
that detects an update to a previous service worker registration, as a way of
letting users know to refresh the page. But those are just minor variations on
the standard few lines of code.&lt;/p&gt;
&lt;p&gt;So, is there any nuance to &lt;code&gt;navigator.serviceWorker.register&lt;/code&gt;? Are there any
best practices to follow? Not surprisingly (given that this article doesn&#39;t end
right here), the answer to both is &amp;quot;yes!&amp;quot;&lt;/p&gt;
&lt;h2 id=&quot;a-users-first-visit&quot;&gt;A user&#39;s first visit &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-registration/#a-users-first-visit&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&#39;s consider a user&#39;s first visit to a web app. There&#39;s no service worker yet,
and the browser has no way of knowing in advance whether there will be a service
worker that is eventually installed.&lt;/p&gt;
&lt;p&gt;As a developer, your priority should be to make sure that the browser quickly
gets the minimal set of critical resources needed to display an interactive
page. Anything that slows down retrieving those responses is the enemy of a
speedy time-to-interactive experience.&lt;/p&gt;
&lt;p&gt;Now imagine that in the process of downloading the JavaScript or images that
your page needs to render, your browser decides to start a background thread or
process (for the sake of brevity, we&#39;ll assume it&#39;s a thread). Assume that
you&#39;re not on a beefy desktop machine, but rather the type of underpowered
mobile phone that much of the world considers their primary device. Spinning up
this extra thread adds contention for CPU time and memory that your browser
might otherwise spend on rendering an interactive web page.&lt;/p&gt;
&lt;p&gt;An idle background thread is unlikely to make a significant difference. But what
if that thread isn&#39;t idle, but instead decides that it&#39;s also going to start
downloading resources from the network? Any concern about CPU or memory
contention should take a backseat to worries about the limited bandwidth
available to many mobile devices. Bandwidth is precious, so don&#39;t undermine
critical resources by downloading secondary resources at the same time.&lt;/p&gt;
&lt;p&gt;All of this is to say that spinning up a new service worker thread to download
and cache resources in the background can work against your goal of providing
the shortest time-to-interactive experience the first time a user visits your
site.&lt;/p&gt;
&lt;h2 id=&quot;improving-the-boilerplate&quot;&gt;Improving the boilerplate &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-registration/#improving-the-boilerplate&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The solution is to control start of the service worker by choosing when to call
&lt;code&gt;navigator.serviceWorker.register()&lt;/code&gt;. A simple rule of thumb would be to delay
registration until after the &lt;a href=&quot;https://developer.mozilla.org//docs/Web/API/GlobalEventHandlers/onload&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;load event&lt;/code&gt;&lt;/a&gt;
fires on &lt;code&gt;window&lt;/code&gt;, like so:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;serviceWorker&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    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;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;But the right time to kick off the service worker registration can also depend
on what your web app is doing right after it loads. For example, the &lt;a href=&quot;https://events.google.com/io2016/&quot; rel=&quot;noopener&quot;&gt;Google I/O
2016 web app&lt;/a&gt; features a short animation
before transitioning to the main screen. Our team
&lt;a href=&quot;https://developers.google.com/web/showcase/2016/iowa2016&quot; rel=&quot;noopener&quot;&gt;found&lt;/a&gt; that kicking
off the service worker registration during the animation could lead to jankiness
on low-end mobile devices. Rather than giving users a poor experience, we
&lt;a href=&quot;https://github.com/GoogleChrome/ioweb2016/blob/8cfa27261f9d07fe8a5bb7d228bd3f35dfc9a91e/app/scripts/helper/elements.js#L42&quot; rel=&quot;noopener&quot;&gt;delayed&lt;/a&gt;
service worker registration until after the animation, when the browser was most
likely to have a few idle seconds.&lt;/p&gt;
&lt;p&gt;Similarly, if your web app uses a framework that performs additional setup after
the page has loaded, look for a framework-specific event that signals when that
work is done.&lt;/p&gt;
&lt;h2 id=&quot;subsequent-visits&quot;&gt;Subsequent visits &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-registration/#subsequent-visits&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;ve been focusing on the first visit experience up until now, but what impact
does delayed service worker registration have on repeat visits to your site?
While it might surprise some folks, there shouldn&#39;t be any impact at all.&lt;/p&gt;
&lt;p&gt;When a service worker is registered, it goes through the &lt;code&gt;install&lt;/code&gt; and
&lt;code&gt;activate&lt;/code&gt; &lt;a href=&quot;https://web.dev/service-worker-lifecycle/&quot;&gt;lifecycle
events&lt;/a&gt;.
Once a service worker is activated, it can handle &lt;code&gt;fetch&lt;/code&gt; events for any
subsequent visits to your web app. The service worker starts &lt;em&gt;before&lt;/em&gt; the
request for any pages under its scope is made, which makes sense when you think
about it. If the existing service worker weren&#39;t already running prior to
visiting a page, it wouldn&#39;t have a chance to fulfill &lt;code&gt;fetch&lt;/code&gt; events for
navigation requests.&lt;/p&gt;
&lt;p&gt;So once there&#39;s an active service worker, it doesn&#39;t matter when you call
&lt;code&gt;navigator.serviceWorker.register()&lt;/code&gt;, or in fact, &lt;em&gt;whether you call it at all&lt;/em&gt;.
Unless you change the URL of the service worker script,
&lt;code&gt;navigator.serviceWorker.register()&lt;/code&gt; is effectively a
&lt;a href=&quot;https://en.wikipedia.org/wiki/NOP&quot; rel=&quot;noopener&quot;&gt;no-op&lt;/a&gt; during subsequent visits. When it&#39;s
called is irrelevant.&lt;/p&gt;
&lt;h2 id=&quot;reasons-to-register-early&quot;&gt;Reasons to register early &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-registration/#reasons-to-register-early&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Are there any scenarios in which registering your service worker as early as
possible makes sense? One that comes to mind is when your service worker uses
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Clients/claim&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;clients.claim()&lt;/code&gt;&lt;/a&gt;
to take control of the page during the first visit, and the service worker
aggressively performs &lt;a href=&quot;https://web.dev/offline-cookbook/#on-network-response/&quot;&gt;runtime
caching&lt;/a&gt;
inside of its &lt;code&gt;fetch&lt;/code&gt; handler. In that situation, there&#39;s an
advantage to getting the service worker active as quickly as possible, to try to
populate its runtime caches with resources that might come in handy later. If
your web app falls into this category, it&#39;s worth taking a step back to make
sure that your service worker&#39;s &lt;code&gt;install&lt;/code&gt; handler doesn&#39;t request
resources that fight for bandwidth with the main page&#39;s requests.&lt;/p&gt;
&lt;h2 id=&quot;testing-things-out&quot;&gt;Testing things out &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-registration/#testing-things-out&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A great way to simulate a first visit is to open your web app in a &lt;a href=&quot;https://support.google.com/chromebook/answer/95464?co=GENIE.Platform%3DDesktop&quot; rel=&quot;noopener&quot;&gt;Chrome
Incognito
window&lt;/a&gt;,
and look at the network traffic in &lt;a href=&quot;https://developer.chrome.com/docs/devtools/&quot; rel=&quot;noopener&quot;&gt;Chrome&#39;s
DevTools&lt;/a&gt;. As a web
developer, you probably reload a local instance of your web app dozens and
dozens of times a day. But by revisiting your site when there&#39;s already a
service worker and fully populated caches, you don&#39;t get the same experience
that a new user would get, and it&#39;s easy to ignore a potential problem.&lt;/p&gt;
&lt;p&gt;Here&#39;s an example illustrating the difference that registration timing could
make. Both screenshots are taken while visiting a &lt;a href=&quot;https://github.com/GoogleChrome/sw-precache/tree/master/app-shell-demo&quot; rel=&quot;noopener&quot;&gt;sample
app&lt;/a&gt; in
Incognito mode using network throttling to simulate a slow connection.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Network traffic with early registration.&quot; decoding=&quot;async&quot; height=&quot;260&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 481px) 481px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gYt2Mc2d03PmhxEcpqrb.png?auto=format&amp;w=962 962w&quot; width=&quot;481&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The screenshot above reflects the network traffic when the sample was  modified
to perform service worker registration as soon as possible. You can see
precaching requests (the entries with the &lt;a href=&quot;http://stackoverflow.com/questions/33590378/status-code200-ok-from-serviceworker-in-chrome-network-devtools/33655173#33655173&quot; rel=&quot;noopener&quot;&gt;gear
icon&lt;/a&gt;
next to them, originating from the service worker&#39;s &lt;code&gt;install&lt;/code&gt; handler)
interspersed with requests for the other resources needed to display the page.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Network traffic with late registration.&quot; decoding=&quot;async&quot; height=&quot;210&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 593px) 593px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/hqRuGzhEtWDm5EXJ88CE.png?auto=format&amp;w=1186 1186w&quot; width=&quot;593&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;In the screenshot above, service worker registration was delayed until after the
page had loaded. You can see that the precaching requests don&#39;t start until all
the resources have been fetched from the network, eliminating any contention for
bandwidth. Moreover, because some of the items we&#39;re precaching are already in
the browser&#39;s HTTP cache—the items with &lt;code&gt;(from disk cache)&lt;/code&gt; in the Size
column—we can populate the service worker&#39;s cache without having to go to the
network again.&lt;/p&gt;
&lt;p&gt;Bonus points if you run this sort of test from an actual, low-end device on a
real mobile network. You can take advantage of Chrome&#39;s &lt;a href=&quot;https://developer.chrome.com/docs/devtools/remote-debugging/&quot; rel=&quot;noopener&quot;&gt;remote debugging
capabilities&lt;/a&gt;
to attach an Android phone to your desktop machine via USB, and ensure that the
tests you&#39;re running actually reflect the real-world experience of many of your
users.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-registration/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To summarize, making sure that your users have the best first-visit experience
should be a top priority. Delaying service worker registration until after the
page has loaded during the initial visit can help ensure that. You&#39;ll still get
all the benefits of having a service worker for your repeat visits.&lt;/p&gt;
&lt;p&gt;A straightforward way to ensure to delay your service worker&#39;s initial
registration until after the first page has loaded is to use the following:&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;serviceWorker&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    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;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
  
  <entry>
    <title>Service Workers in Production</title>
    <link href="https://web.dev/service-workers-iowa/"/>
    <updated>2015-10-01T00:00:00Z</updated>
    <id>https://web.dev/service-workers-iowa/</id>
    <content type="html" mode="escaped">&lt;figure&gt;
&lt;img alt=&quot;Portrait screenshot&quot; decoding=&quot;async&quot; height=&quot;1353&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/HhySNupQxJmqcouugbqS.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#summary&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Learn how we used service worker libraries to make the Google I/O 2015 web app
fast, and offline-first.&lt;/p&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#overview&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This year’s &lt;a href=&quot;https://events.google.com/io2015/&quot; rel=&quot;noopener&quot;&gt;Google I/O 2015 web app&lt;/a&gt; was
written by Google’s Developer Relations team, based on designs by our friends
at &lt;a href=&quot;http://www.instrument.com/&quot; rel=&quot;noopener&quot;&gt;Instrument&lt;/a&gt;, who wrote the nifty
&lt;a href=&quot;http://www.instrument.com/news/google-io-2015&quot; rel=&quot;noopener&quot;&gt;audio/visual experiment&lt;/a&gt;. Our
team’s mission was to ensure that the I/O web app (which I’ll refer to by
its codename, IOWA) showcased everything the modern web could do. A full
offline-first experience was at the top of our list of must-have features.&lt;/p&gt;
&lt;p&gt;If you’ve read any of the other articles on this site recently, you’ve
undoubtedly encountered &lt;a href=&quot;https://developers.google.com/web/fundamentals/getting-started/primers/service-workers&quot; rel=&quot;noopener&quot;&gt;service workers&lt;/a&gt;,
and you won’t be surprised to hear that IOWA’s offline support is heavily
reliant on them. Motivated by the real-world needs of IOWA, we developed two
libraries to handle two different offline use cases:
&lt;a href=&quot;https://github.com/GoogleChrome/sw-precache&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;sw-precache&lt;/code&gt;&lt;/a&gt; to automate
precaching of static resources, and
&lt;a href=&quot;https://github.com/GoogleChrome/sw-toolbox&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;sw-toolbox&lt;/code&gt;&lt;/a&gt; to handle
runtime caching and fallback strategies.&lt;/p&gt;
&lt;p&gt;The libraries complement each other nicely, and allowed us to implement a
performant strategy in which IOWA’s static content “shell” was always served
directly from the cache, and dynamic or remote resources were served from the
network, with fallbacks to cached or static responses when needed.&lt;/p&gt;
&lt;h2 id=&quot;precaching-with-sw-precache&quot;&gt;Precaching with &lt;code&gt;sw-precache&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#precaching-with-sw-precache&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;IOWA’s static resources—its HTML, JavaScript, CSS, and images—provide the core
shell for the web application. There were two specific requirements that were
important when thinking about caching these resources: we wanted to make sure
that most static resources were cached, and that they were kept up to date.
&lt;a href=&quot;https://github.com/GoogleChrome/sw-precache&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;sw-precache&lt;/code&gt;&lt;/a&gt; was built with those
requirements in mind.&lt;/p&gt;
&lt;h3 id=&quot;build-time-integration&quot;&gt;Build-time Integration &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#build-time-integration&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sw-precache&lt;/code&gt; with IOWA’s &lt;a href=&quot;http://gulpjs.com/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;gulp&lt;/code&gt;&lt;/a&gt;-based build process,
and we rely on a series of &lt;a href=&quot;https://github.com/isaacs/node-glob&quot; rel=&quot;noopener&quot;&gt;glob&lt;/a&gt; patterns
to ensure that we generate a complete list of all the static resources IOWA uses.&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 literal-property property&quot;&gt;staticFileGlobs&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;    rootDir &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/bower_components/**/*.{html,js,css}&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    rootDir &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/elements/**&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    rootDir &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/fonts/**&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    rootDir &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/images/**&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    rootDir &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/scripts/**&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    rootDir &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/styles/**/*.css&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    rootDir &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/data-worker-scripts.js&#39;&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;Alternative approaches, like hard coding a list of file names into an array,
and remembering to bump a cache version number each time any of those files
changes were far too error prone, especially given that we had
&lt;a href=&quot;https://events.google.com/io2015/humans.txt&quot; rel=&quot;noopener&quot;&gt;multiple team members&lt;/a&gt; checking
in code. No one wants to break offline support by leaving out a new file in a
manually maintained array! Build-time integration meant we could make
changes to existing files and add new files without having those worries.&lt;/p&gt;
&lt;h3 id=&quot;updating-cached-resources&quot;&gt;Updating Cached Resources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#updating-cached-resources&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sw-precache&lt;/code&gt; generates a base &lt;a href=&quot;https://events.google.com/io2015/service-worker.js&quot; rel=&quot;noopener&quot;&gt;service worker script&lt;/a&gt;
that includes a unique &lt;a href=&quot;https://en.wikipedia.org/wiki/MD5&quot; rel=&quot;noopener&quot;&gt;MD5 hash&lt;/a&gt; for each
resource that gets precached. Each time an existing resource changes,
or a new resource is added, the service worker script is regenerated. This
automatically triggers the &lt;a href=&quot;https://developers.google.com/web/fundamentals/getting-started/primers/service-workers#lifecycle&quot; rel=&quot;noopener&quot;&gt;service worker update flow&lt;/a&gt;,
in which the new resources are cached and out of date resources are purged.
Any existing resources that have identical MD5 hashes are left as-is. That
means users who have visited the site before only end up downloading the
minimal set of changed resources, leading to a much more efficient experience
than if the entire cache was expired &lt;em&gt;en masse&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Each file that matches one of the glob patterns is downloaded and cached the
first time a user visits IOWA. We made an effort to ensure that only critical
resources needed to render the page were precached. Secondary content, like the
media used in the &lt;a href=&quot;http://www.instrument.com/news/google-io-2015&quot; rel=&quot;noopener&quot;&gt;audio/visual experiment&lt;/a&gt;,
or the profile images of the sessions’ speakers, were deliberately not
precached, and we instead used the &lt;a href=&quot;https://github.com/GoogleChrome/sw-toolbox&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;sw-toolbox&lt;/code&gt;&lt;/a&gt;
library to handle offline requests for those resources.&lt;/p&gt;
&lt;h2 id=&quot;sw-toolbox,-for-all-our-dynamic-needs&quot;&gt;&lt;code&gt;sw-toolbox&lt;/code&gt;, for All Our Dynamic Needs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#sw-toolbox,-for-all-our-dynamic-needs&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As mentioned, precaching every resource that a site needs to work offline isn’t
feasible. Some resources are too large or infrequently used to make it
worthwhile, and other resources are dynamic, like the responses from a remote
API or service. But just because a request isn’t precached doesn’t mean it has
to result in a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/DOMException#exception-NetworkError&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;NetworkError&lt;/code&gt;&lt;/a&gt;.
&lt;a href=&quot;https://github.com/GoogleChrome/sw-toolbox&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;sw-toolbox&lt;/code&gt;&lt;/a&gt; gave us the
flexibility to implement &lt;a href=&quot;https://github.com/GoogleChrome/sw-toolbox#request-handlers&quot; rel=&quot;noopener&quot;&gt;request handlers&lt;/a&gt;
that handle runtime caching for some resources and custom fallbacks for
others. We also used it to update our previously cached resources in response
to push notifications.&lt;/p&gt;
&lt;p&gt;Here are a few examples of custom request handlers that we built on top of
sw-toolbox. It was easy to integrate them with the base service worker script
via &lt;code&gt;sw-precache&lt;/code&gt;’s &lt;a href=&quot;https://github.com/GoogleChrome/sw-precache#importscripts-arraystring&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;importScripts parameter&lt;/code&gt;&lt;/a&gt;,
which pulls standalone JavaScript files into the scope of the service worker.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The &lt;a href=&quot;https://github.com/GoogleChrome/ioweb2015&quot;&gt;GitHub source code&lt;/a&gt; for this app uses an previous version of &lt;code&gt;sw-toolbox&lt;/code&gt; named &lt;code&gt;shed; you can assume that the older &lt;/code&gt;shed&lt;code&gt;interface and the newer&lt;/code&gt;toolbox` interface can be used interchangeably. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;audiovisual-experiment&quot;&gt;Audio/Visual Experiment &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#audiovisual-experiment&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For the &lt;a href=&quot;https://github.com/GoogleChrome/ioweb2015/blob/master/app/scripts/shed/experiment.js&quot; rel=&quot;noopener&quot;&gt;audio/visual experiment&lt;/a&gt;,
we used &lt;code&gt;sw-toolbox&lt;/code&gt;’s &lt;a href=&quot;https://github.com/GoogleChrome/sw-toolbox#toolboxnetworkfirst&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;networkFirst&lt;/code&gt;&lt;/a&gt;
cache strategy. All HTTP requests matching the URL pattern for the experiment
would first be made against the network, and if a successful response was
returned, that response would then be stashed away using the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Cache&quot; rel=&quot;noopener&quot;&gt;Cache Storage API&lt;/a&gt;.
If a subsequent request was made when the network was unavailable, the
previously cached response would be used.&lt;/p&gt;
&lt;p&gt;Because the cache was automatically updated each time a successful network
response came back, we didn’t have to specifically version resources or expire
entries.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;router&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;/experiment/(.+)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;networkFirst&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;speaker-profile-images&quot;&gt;Speaker Profile Images &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#speaker-profile-images&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For speaker profile images, our goal was to display a previously cached version of a
given speaker’s image if it was available, falling back to the network to retrieve the
image if it wasn’t. If that network request failed, as a final fallback, we used a
generic placeholder image that was precached (and therefore would always be
available). This is a common strategy to use when dealing with images that
could be replaced with a generic placeholder, and it was easy to implement by
chaining &lt;code&gt;sw-toolbox&lt;/code&gt;’s
&lt;a href=&quot;https://github.com/GoogleChrome/sw-toolbox#toolboxcachefirst&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;cacheFirst&lt;/code&gt;&lt;/a&gt; and
&lt;a href=&quot;https://github.com/GoogleChrome/sw-toolbox#toolboxcacheonly&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;cacheOnly&lt;/code&gt;&lt;/a&gt; handlers.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_PROFILE_IMAGE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;images/touch/homescreen96.png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;profileImageRequest&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&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cacheFirst&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;&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cacheOnly&lt;/span&gt;&lt;span class=&quot;token punctuation&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;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DEFAULT_PROFILE_IMAGE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;precache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DEFAULT_PROFILE_IMAGE&lt;/span&gt;&lt;span class=&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;toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;router&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;/(.+)/images/speakers/(.*)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    profileImageRequest&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 literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&lt;span class=&quot;token char-set class-name&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token quantifier number&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token special-escape escape&quot;&gt;\.&lt;/span&gt;googleapis&lt;span class=&quot;token special-escape escape&quot;&gt;\.&lt;/span&gt;com&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;figure&gt;
  &lt;img alt=&quot;Profile images from a session page&quot; decoding=&quot;async&quot; height=&quot;502&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gIn35LPfj3XD1OFG09QP.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    Profile images from a &lt;a href=&quot;https://events.google.com/io2015/schedule?filters=Develop%20%26%20Design%2CChrome%20%2F%20Web&amp;sid=1b718f8b-b6d4-e411-b87f-00155d5066d7#day1/1b718f8b-b6d4-e411-b87f-00155d5066d7&quot;&gt;session page&lt;/a&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;updates-to-users-schedules&quot;&gt;Updates to Users’ Schedules &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#updates-to-users-schedules&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the key features of IOWA was allowing signed-in users to create and
maintain a schedule of sessions they planned on attending. Like you’d expect,
session updates were made via HTTP &lt;code&gt;POST&lt;/code&gt; requests to a backend server, and we
spent some time working out the best way to handle those state-modifying
requests when the user is offline. We came up with a combination of a
that queued failed requests in IndexedDB, coupled with logic in the main web page
that checked IndexedDB for queued requests and retried any that it found.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DB_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;shed-offline-session-updates&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;queueFailedSessionUpdateRequest&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&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;    simpleDB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DB_NAME&lt;/span&gt;&lt;span class=&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;db&lt;/span&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;    db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&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;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleSessionUpdateRequest&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&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; global&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&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;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Response&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 punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;queueFailedSessionUpdateRequest&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;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/(.+)api/v1/user/schedule/(.+)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    handleSessionUpdateRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/(.+)api/v1/user/schedule/(.+)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                        handleSessionUpdateRequest&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;Because the retries were made from the context of the main page, we could be
sure that they included a fresh set of user credentials. Once the retries
succeeded, we displayed a message to let the user know that their previously
queued updates had been applied.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;simpleDB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;QUEUED_SESSION_UPDATES_DB_NAME&lt;/span&gt;&lt;span class=&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; replayPromises &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; db&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; promise &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;IOWA&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;&lt;span class=&quot;token function&quot;&gt;xhrPromise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;method&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token 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; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token 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 boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;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;    replayPromises&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;promise&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;replayPromises&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;replayPromises&lt;span class=&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token constant&quot;&gt;IOWA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Elements&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Toast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token string&quot;&gt;&#39;My Schedule was updated with offline changes.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token 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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token constant&quot;&gt;IOWA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Elements&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Toast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;Offline changes could not be applied to My Schedule.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;offline-google-analytics&quot;&gt;Offline Google Analytics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#offline-google-analytics&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In a similar vein, we implemented a handler to queue any failed Google
Analytics requests and attempt to replay them later, when the network was
hopefully available. With this approach, being offline doesn’t mean sacrificing
the insights Google Analytics offers. We added the &lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#qt&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;qt&lt;/code&gt;&lt;/a&gt;
parameter to each queued request, set to the amount of time that had passed
since the request was first attempted, to ensure that a proper event
attribution time made it to the Google Analytics backend. Google Analytics
&lt;a href=&quot;https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#qt&quot; rel=&quot;noopener&quot;&gt;officially supports&lt;/a&gt;
values for &lt;code&gt;qt&lt;/code&gt; of up to only 4 hours, so we made a best-effort attempt to replay those
requests as soon as possible, each time the service worker started up.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DB_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;offline-analytics&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;EXPIRATION_TIME_DELTA&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;86400000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ORIGIN&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;https&lt;span class=&quot;token quantifier number&quot;&gt;?&lt;/span&gt;:&lt;span class=&quot;token escape&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;token escape&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;token group punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token group punctuation&quot;&gt;(&lt;/span&gt;www&lt;span class=&quot;token alternation keyword&quot;&gt;|&lt;/span&gt;ssl&lt;span class=&quot;token group punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token special-escape escape&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;token group punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token quantifier number&quot;&gt;?&lt;/span&gt;google-analytics&lt;span class=&quot;token special-escape escape&quot;&gt;\.&lt;/span&gt;com&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;replayQueuedAnalyticsRequests&lt;/span&gt;&lt;span class=&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;    simpleDB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DB_NAME&lt;/span&gt;&lt;span class=&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;db&lt;/span&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;    db&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; originalTimestamp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; timeDelta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&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; originalTimestamp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; replayUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&amp;amp;qt=&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; timeDelta&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;replayUrl&lt;span class=&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Response&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 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;        db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeDelta &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;EXPIRATION_TIME_DELTA&lt;/span&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;            db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;queueFailedAnalyticsRequest&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&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;    simpleDB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DB_NAME&lt;/span&gt;&lt;span class=&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;db&lt;/span&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;    db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&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;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleAnalyticsCollectionRequest&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&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; global&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&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;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Response&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 punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;queueFailedAnalyticsRequest&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;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;router&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;/collect&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    handleAnalyticsCollectionRequest&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 literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ORIGIN&lt;/span&gt;&lt;span class=&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;toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;router&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;/analytics.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;networkFirst&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 literal-property property&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ORIGIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;replayQueuedAnalyticsRequests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;push-notification-landing-pages&quot;&gt;Push Notification Landing Pages &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#push-notification-landing-pages&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Service workers didn’t just handle IOWA’s offline functionality—they also
powered the &lt;a href=&quot;https://developers.google.com/web/updates/2015/03/push-notifications-on-the-open-web&quot; rel=&quot;noopener&quot;&gt;push notifications&lt;/a&gt;
that we used to notify users about updates to their bookmarked sessions. The
landing page associated with those notifications displayed the updated session
details. Those landing pages were already being cached as part of the overall
site, so they already worked offline, but we needed to make sure that the
session details on that page were up to date, even when viewed offline. To do
that, we modified previously cached session metadata with the updates that
triggered the push notification, and we stored the result in the cache. This
up-to-date info will be used the next time the session details page is opened,
whether that takes place online or offline.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cacheName&lt;span class=&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;cache&lt;/span&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;    cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;api/v1/schedule&#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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;parseResponseJSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;schedule&lt;/span&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;        sessions&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;session&lt;/span&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;            schedule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessions&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; session&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;        cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;api/v1/schedule&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&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;schedule&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;api/v1/schedule&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;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;h2 id=&quot;gotchas-and-considerations&quot;&gt;Gotchas &amp;amp; Considerations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#gotchas-and-considerations&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Of course, no one works on a project of IOWA’s scale without running into a few
gotchas. Here are some of the ones we ran into, and how we worked around them.&lt;/p&gt;
&lt;h3 id=&quot;stale-content&quot;&gt;Stale Content &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#stale-content&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Whenever you’re planning a caching strategy, whether implemented via service
workers or with the standard browser cache, there’s a tradeoff between
delivering resources as quickly as possible versus delivering the freshest
resources. Via &lt;code&gt;sw-precache&lt;/code&gt;, we implemented an aggressive cache-first
strategy for our application’s shell, meaning our service worker would not check the
network for updates before returning the HTML, JavaScript, and CSS on the page.&lt;/p&gt;
&lt;p&gt;Fortunately, we were able to take advantage of &lt;a href=&quot;https://developers.google.com/web/fundamentals/getting-started/primers/service-workers#lifecycle&quot; rel=&quot;noopener&quot;&gt;service worker lifecycle events&lt;/a&gt;
to detect when new content was available after the page had already loaded.
When an updated service worker is detected, we display a
&lt;a href=&quot;https://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-usage&quot; rel=&quot;noopener&quot;&gt;toast message&lt;/a&gt;
to the user letting them know that they should reload their page to see the
newest content.&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;controller&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onstatechange&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;redundant&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;tapHandler&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token constant&quot;&gt;IOWA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Elements&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Toast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&#39;Tap here or refresh the page for the latest content.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        tapHandler&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;figure&gt;
  &lt;img alt=&quot;The latest content toast&quot; decoding=&quot;async&quot; height=&quot;159&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NGVMuSfcRkhBcdxnGJLw.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;The &quot;latest content&quot; toast.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;make-sure-static-content-is-static&quot;&gt;Make Sure Static Content is Static! &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#make-sure-static-content-is-static&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sw-precache&lt;/code&gt; uses an MD5 hash of local files’ contents, and only fetches
resources whose hash has changed. This means that resources are available on the page
almost immediately, but it also means that once something is cached, it’s going to
stay cached until it’s assigned a new hash in an updated service worker script.&lt;/p&gt;
&lt;p&gt;We &lt;a href=&quot;https://github.com/GoogleChrome/ioweb2015/issues/1504&quot; rel=&quot;noopener&quot;&gt;ran into an issue&lt;/a&gt;
with this behavior during I/O due to our backend needing to dynamically update
the livestream YouTube video IDs for each day of the conference. Because the
&lt;a href=&quot;https://github.com/GoogleChrome/ioweb2015/blob/d2ec7f1a86123483acd1d07fb2c7f84f2413b195/app/templates/layout_full.html#L385&quot; rel=&quot;noopener&quot;&gt;underlying template file&lt;/a&gt;
was static and didn’t change, our service worker update flow wasn’t triggered,
and what was meant to be a dynamic response from the server with updating
YouTube videos ended up being the cached response for a number of users.&lt;/p&gt;
&lt;p&gt;You can avoid this type of issue by making sure your web application is
structured so that the shell is always static and can be safely precached,
while any dynamic resources which modify the shell are loaded independently.&lt;/p&gt;
&lt;h3 id=&quot;cache-bust-your-precaching-requests&quot;&gt;Cache-bust Your Precaching Requests &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#cache-bust-your-precaching-requests&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When &lt;code&gt;sw-precache&lt;/code&gt; makes requests for resources to precache, it uses those
responses indefinitely as long as it thinks that the MD5 hash for the file hasn’t
changed. This means that it’s particularly important to make sure that the response to
the precaching request is a fresh one, and not returned from the browser’s HTTP
cache. (Yes, &lt;code&gt;fetch()&lt;/code&gt; requests made in a service worker can respond with
data from the browser’s HTTP cache.)&lt;/p&gt;
&lt;p&gt;To ensure that responses we precache are straight from the network and not the
browser’s HTTP cache, &lt;code&gt;sw-precache&lt;/code&gt; automatically
&lt;a href=&quot;https://github.com/GoogleChrome/sw-precache/blob/16acd75940a83d97b1069b7be00522405bce0690/service-worker.tmpl#L67&quot; rel=&quot;noopener&quot;&gt;appends a cache-busting query parameter&lt;/a&gt;
to each URL it requests. If you’re not using &lt;code&gt;sw-precache&lt;/code&gt; and you are
making use of a cache-first response strategy, make sure that you
&lt;a href=&quot;https://github.com/GoogleChrome/samples/blob/e4df12c8642381243b6c1710c41394d85b33d82f/service-worker/prefetch/service-worker.js#L56&quot; rel=&quot;noopener&quot;&gt;do something similar&lt;/a&gt;
in your own code!&lt;/p&gt;
&lt;p&gt;A cleaner solution to cache-busting would be to set the
&lt;a href=&quot;https://fetch.spec.whatwg.org/#concept-request-cache-mode&quot; rel=&quot;noopener&quot;&gt;cache mode&lt;/a&gt;
of each &lt;code&gt;Request&lt;/code&gt; used for precaching to &lt;code&gt;reload&lt;/code&gt;, which will ensure that the
response comes from the network. However, as of this writing, the cache mode
option &lt;a href=&quot;https://code.google.com/p/chromium/issues/detail?id=453190#c10&quot; rel=&quot;noopener&quot;&gt;isn’t supported&lt;/a&gt;
in Chrome.&lt;/p&gt;
&lt;h3 id=&quot;support-for-logging-in-and-out&quot;&gt;Support for Logging In &amp;amp; Out &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#support-for-logging-in-and-out&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;IOWA allowed users to log in using their Google Accounts and update their
customized event schedules, but that also meant that users might later log out.
Caching personalized response data is obviously a tricky topic, and there’s not
always a single right approach.&lt;/p&gt;
&lt;p&gt;Since viewing your personal schedule, even when offline, was core to the IOWA
experience, we decided that using cached data was appropriate. When a user signs out,
we made sure to clear previously cached session data.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;    self&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;message&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token 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;data &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;clear-cached-user-data&#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;        caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;toolbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cacheName&lt;span class=&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;cache&lt;/span&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;          cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&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;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;requests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; requests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;indexOf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;api/v1/user/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;userDataRequests&lt;/span&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;            userDataRequests&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 keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;userDataRequest&lt;/span&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;              cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userDataRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;watch-out-for-extra-query-parameters&quot;&gt;Watch Out for Extra Query Parameters! &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#watch-out-for-extra-query-parameters&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When a service worker checks for a cached response, it uses a request URL as the key.
By default, the request URL must exactly match the URL used to store the cached response, including
any query parameters in the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/URLUtils/search&quot; rel=&quot;noopener&quot;&gt;search&lt;/a&gt;
portion of the URL.&lt;/p&gt;
&lt;p&gt;This ended up causing an issue for us during development, when we started using
&lt;a href=&quot;https://support.google.com/analytics/answer/1033867&quot; rel=&quot;noopener&quot;&gt;URL parameters&lt;/a&gt; to keep track of where our
traffic was coming from. For example, we &lt;a href=&quot;https://github.com/GoogleChrome/ioweb2015/blob/28113917b88436dd569c39fd5eef184b6aefdd1c/app/scripts/shed/push-notifications.js#L32&quot; rel=&quot;noopener&quot;&gt;added&lt;/a&gt;
the &lt;code&gt;utm_source=notification&lt;/code&gt; parameter to URLs that were opened when clicking on one of our
notifications, and used &lt;code&gt;utm_source=web_app_manifest&lt;/code&gt; in the &lt;a href=&quot;https://github.com/GoogleChrome/ioweb2015/blob/0bab714dbb08927f901420fc05b43b9f97f7ddc3/app/templates/manifest.json#L4&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;start_url&lt;/code&gt;&lt;/a&gt;
for our &lt;a href=&quot;https://developers.google.com/web/updates/2014/11/Support-for-installable-web-apps-with-webapp-manifest-in-chrome-38-for-Android&quot; rel=&quot;noopener&quot;&gt;web app manifest&lt;/a&gt;.
URLs which previously matched cached responses were coming up as misses when those parameters
were appended.&lt;/p&gt;
&lt;p&gt;This is partially addressed by the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Cache/match#Parameters&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ignoreSearch&lt;/code&gt;&lt;/a&gt;
option which can be used when calling &lt;code&gt;Cache.match()&lt;/code&gt;. Unfortunately, Chrome &lt;a href=&quot;https://developers.google.com/web/updates/2015/09/updates-to-cache-api#cache-query-options-coming-to-chrome-soon&quot; rel=&quot;noopener&quot;&gt;doesn&#39;t yet&lt;/a&gt;
support &lt;code&gt;ignoreSearch&lt;/code&gt;, and even if it did, it&#39;s an all-or-nothing behavior. What we needed was a
way to ignore &lt;em&gt;some&lt;/em&gt; URL query parameters while taking others that were meaningful into account.&lt;/p&gt;
&lt;p&gt;We ended up extending &lt;code&gt;sw-precache&lt;/code&gt; to strip out some query parameters before checking for a cache
match, and allow developers to customize which parameters are ignored via the
&lt;a href=&quot;https://github.com/GoogleChrome/sw-precache#ignoreurlparametersmatching-arrayregex&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ignoreUrlParametersMatching&lt;/code&gt;&lt;/a&gt; option.
Here&#39;s the underlying implementation:&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;stripIgnoredUrlParameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;originalUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ignoredRegexes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; url &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;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;originalUrl&lt;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;    url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&amp;amp;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;kv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; kv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;=&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;kv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ignoredRegexes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;every&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;ignoredRegex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;ignoredRegex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;kv&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;kv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; kv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;=&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&amp;amp;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;what-this-means-for-you&quot;&gt;What This Means for You &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/service-workers-iowa/#what-this-means-for-you&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The service worker integration in the Google I/O Web App is likely the most
complex, real-world usage that has been deployed to this point. We’re looking
forward to the web developer community using the tools we created
&lt;a href=&quot;https://github.com/GoogleChrome/sw-precache&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;sw-precache&lt;/code&gt;&lt;/a&gt; and
&lt;a href=&quot;https://github.com/GoogleChrome/sw-toolbox&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;sw-toolbox&lt;/code&gt;&lt;/a&gt; as well as the
techniques we’re describing to power your own web applications.
Service workers are a &lt;a href=&quot;https://en.wikipedia.org/wiki/Progressive_enhancement&quot; rel=&quot;noopener&quot;&gt;progressive enhancement&lt;/a&gt;
that you can start using today, and when used as part of a properly structured
web app, the speed and offline benefits are significant for your users.&lt;/p&gt;
</content>
    <author>
      <name>Jeff Posnick</name>
    </author>
  </entry>
</feed>
