# Reliable

# index

---
layout: collection
override:tags: []
date: 2018-11-05
pathName: reliable
---

# Adaptive loading with service workers



https://www.youtube.com/watch?v=fhqCwDP69PI

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.

[Adaptive loading](/adaptive-loading-cds-2019/) is a web performance pattern that lets you adapt your site based on the user's network and device conditions.

The adaptive loading pattern is made possible by [service workers](/service-workers-cache-storage/), the [Network Information API](https://developer.mozilla.org/docs/Web/API/Network_Information_API), the [Hardware Concurrency API](https://developer.mozilla.org/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency), and the [Device Memory API](https://developer.mozilla.org/docs/Web/API/Navigator/deviceMemory). In this guide we explore how you can use service workers and the Network Information API to achieve an adaptive loading strategy.

## Production case

[Terra](https://www.terra.com.br/) is one of the biggest media companies in Brazil. It has a large user base, coming from a wide variety of devices and networks.

To provide a more reliable experience to all their users, Terra combines service workers and the [Network Information API](https://developer.mozilla.org/docs/Web/API/Network_Information_API) to deliver lower quality images to users on 2G or 3G connections.

<figure>
  

*A screenshot of Terra's home page connected to different image qualities according to the connection type.*

</figure>

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.

As is the case with many publishers, Terra serves [AMP](https://amp.dev/) 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.

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.

To achieve that, they use the [Network Information API](https://developer.mozilla.org/docs/Web/API/Network_Information_API) in the service worker to detect if the request comes from 3G or slower. If that's the case, they change the URL of the page to request the AMP version of the page instead.

<figure>
  

*A screenshot of Terra's article page connected to different image qualities according to the connection type.*

</figure>

Thanks to this technique, they send **70% less bytes** to users on slower connections. The **time spent** in AMP pages is higher for 3G users and ads in AMP pages have a better **CTR (click-through-rate)** for that group.

## Implement adaptive loading with Workbox

In this section we'll explore how [Workbox](/workbox/) can be used to implement adaptive loading strategies.

Workbox provides several [runtime caching strategies](/runtime-caching-with-workbox/) out of the box. They are used to indicate how the service worker generates a response after receiving a `fetch` event.

For example, in a [Cache First](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#cache-first-cache-falling-back-to-network) strategy the [`Request`](https://developer.mozilla.org/docs/Web/API/Request) will be fulfilled using the cached response (if available). If there isn't a cached response, the `Request` will be fulfilled by a network request and the response will be cached.

```javascript
import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';

registerRoute(
  new RegExp('/img/'),
  new CacheFirst()
);
```

Caching strategies can be customized with [Workbox plugins](https://developer.chrome.com/docs/workbox/using-plugins/). 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 [custom plugin](https://developer.chrome.com/docs/workbox/using-plugins/#methods-for-custom-plugins), and introduce some custom logic of your choice.

To achieve adapting loading, define a custom plugin, called, for example, `adaptiveLoadingPlugin`:

```javascript
const adaptiveLoadingPlugin = {
  requestWillFetch: async ({request}) => {
    const urlParts = request.url.split('/');
    let imageQuality;

    switch (
      navigator && navigator.connection
        ? navigator.connection.effectiveType
        : ''
    ) {
      //...
      case '3g':
        imageQuality = 'q_30';
        break;
      //...
    }

    const newUrl = urlParts
      .splice(urlParts.length - 1, 0, imageQuality)
      .join('/')
      .replace('.jpg', '.png');
    const newRequest = new Request(newUrl.href, {headers: request.headers});

    return newRequest;
  },
};
```

The previous code does the following:

- Implements a `requestWillFetch()` callback: This is called whenever a network request is about to be made, so you can alter the `Request`.
- Checks the connection type, by using the [Network Information API](https://developer.mozilla.org/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency). Based on the status of the network, it creates a new URL part, indicating the quality of the image to fetch (e.g. `q_30` for 3G users).
- Creates a new URL based on the dynamic `newPart` value, and returns the new `Request` to be made, based on that URL.

Next, pass the plugin to a `cacheFirst` strategy containing a regular expression to match image URLs (e.g. `/img/`):

```javascript/5
workbox.routing.registerRoute(
  new RegExp('/img/'),
  workbox.strategies.cacheFirst({
    cacheName: 'images',
    plugins: [
      adaptiveLoadingPlugin,
      workbox.expiration.Plugin({
        maxEntries: 50,
        purgeOnQuotaError: true,
      }),
    ],
  }),
);
```

As a result, when requests for images are intercepted, the runtime caching strategy will try to fulfill the request from the cache. If it's not available, it will run the logic in the plugin, to decide which image quality to fetch from the network.

Finally the response will be persisted in the cache, and sent back to the page.

## Cloudinary Workbox Plugin

Cloudinary, a video and image hosting service, has a [Workbox Plugin](https://www.npmjs.com/package/cloudinary-workbox-plugin) that encapsulates the functionality explained in the previous section, making it even easier to implement.

<figure>
  

*Cloudinary and Workbox logos.*

</figure>

The plugin is designed to work with the [Workbox webpack plugin](https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/). To implement it, use the [`GenerateSW()`](https://developer.chrome.com/docs/workbox/reference/workbox-webpack-plugin/#type-GenerateSW) class:

```javascript
new workboxPlugin.GenerateSW({
  swDest: 'sw.js',
  importScripts: ['./cloudinaryPlugin.js'],
  runtimeCaching: [
    {
      urlPattern: new RegExp('^https://res.cloudinary.com/.*/image/upload/'),
      handler: 'CacheFirst',
      options: {
        cacheName: 'cloudinary-images',
        plugins: [
          {
            requestWillFetch: async ({request}) =>
              cloudinaryPlugin.requestWillFetch(request),
          },
        ],
      },
    },
  ],
});
```

The previous code does the following:

- Uses the `GenerateSW()` class to configure webpack to generate a service worker in the destination indicated in `swDest`.
- Imports the cloudinary plugin script.
- Defines a Cache First runtime caching strategy for requests for images to the Cloudinary CDN.
- Passes the [Cloudinary Workbox Plugin](https://www.npmjs.com/package/cloudinary-workbox-plugin) to adjust the image quality according to the network conditions.

## Explore more adaptive loading strategies

You can go beyond this, by mapping device signals, like [hardware concurrency](https://developer.mozilla.org/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency) and [device memory](https://developer.mozilla.org/docs/Web/API/Navigator/deviceMemory) to device categories and then serving different assets depending on the device type (low-, mid- or high-end).


# App shell UX with service workers and streams



https://www.youtube.com/watch?v=fhqCwDP69PI

[Single-page app (SPA)](https://en.wikipedia.org/wiki/Single-page_application) 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.

This means that the web app doesn't perform an actual page reload. The [History API](https://developer.mozilla.org/docs/Web/API/History_API) is used instead to navigate back and forth through the user's history and manipulate the contents of the history stack.

Using this type of architecture can provide an [app shell UX](/learn/pwa/architecture/) that's fast, reliable, and usually consumes less data when navigating.

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.

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'll analyze how you can achieve an SPA-like architecture in multi-page apps by combining partials, service workers, and streams.

## Production case

[DEV](https://dev.to/) is a community where software developers write articles, take part in discussions, and build their professional profiles.

<figure>
  

*A screenshot of https://dev.to*

</figure>

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't want to undertake a major architectural change or move away from their original tech stack.

Here's how their solution works:

1. First, they create partials of their home page for the header and the footer (`shell_top.html` and `shell_bottom.html`) and deliver them as standalone HTML snippets with an endpoint. These assets are added to the cache at the service worker `install` event (what's commonly referred to as [precaching](/precache-with-workbox/)).
1. When a navigation request is intercepted by the service worker, they create a [streamed response](https://developer.mozilla.org/docs/Web/API/ReadableStream) 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.

<figure>
  

*Dev's architecture consisting on static headers and footers that are cached and a body requested from the network.*

</figure>

The key element of this solution is the usage of [streams](https://developer.chrome.com/blog/sw-readablestreams/), which enables [incremental creations and updates](https://streams.spec.whatwg.org/#intro) 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'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't perceive an actual page refresh, only the new content (the body) being updated.

The resulting UX is similar to the app shell UX pattern of SPAs, implemented on a MPA site.

The previous section contains a quick summary of DEV's solution. For a more detailed explanation check out their [blog post](https://dev.to/devteam/instant-webpages-and-terabytes-of-data-savings-through-the-magic-of-service-workers-1mkc) on this topic.

## Implement an app shell UX architecture in MPAs with Workbox

In this section we'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 [Beyond SPAs: alternative architectures for your PWA](https://developer.chrome.com/blog/beyond-spa/).

### The server

#### Partials

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.

The **head partial** can contain all the logic needed to style and render the header of the page. The **navbar partial** can contain the logic for the navigation bar, the **footer partial** the code that needs to execute there, and so forth.

The first time the user visits the site, your server generates a response by assembling the different parts of the page:

```javascript
app.get(routes.get('index'), async (req, res) => {
  res.write(headPartial + navbarPartial);
  const tag = req.query.tag || DEFAULT_TAG;
  const data = await requestData(…);
  res.write(templates.index(tag, data.items));
  res.write(footPartial);
  res.end();
});
```

By using the `res` (response) object's [write() method](https://nodejs.org/api/http.html#http_response_write_chunk_encoding_callback), and referencing locally stored partial templates, the response can be [streamed](https://github.com/substack/stream-handbook) 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.

The next portion of the page uses API data, which involves a network request. The web app can't render anything else until it gets a response back and processes it, but at least users aren't staring at a blank screen while they wait.

### The service worker

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.

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.

#### Precaching static assets

The first step is to cache the partial HTML templates, so they are immediately available.
With [Workbox precaching](https://developer.chrome.com/docs/workbox/modules/workbox-precaching/) you can store these files at the `install` event of the service worker and keep them up to date when changes are deployed to the web app.

Depending on the build process, Workbox has different solutions to generate a service worker and indicate the list of files to precache, including [webpack](https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/) and [gulp](https://developers.google.com/codelabs/workbox-lab#0) plugins, a [generic node module](https://developer.chrome.com/docs/workbox/modules/workbox-build/) and a [command line interface](https://developer.chrome.com/docs/workbox/modules/workbox-cli/).

For a partials configuration like the one described earlier, the resulting service worker file should contain something similar to the following:

```javascript
workbox.precaching.precacheAndRoute([
  {
    url: 'partials/about.html',
    revision: '518747aad9d7e',
  },
  {
    url: 'partials/foot.html',
    revision: '69bf746a9ecc6',
  },
  // etc.
]);
```

#### Streaming

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 [Streams API](https://developer.mozilla.org/docs/Web/API/Streams_API) within our service worker makes that possible.
[Workbox Streams](https://developer.chrome.com/docs/workbox/reference/workbox-streams/) 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.

First, set up the strategies in Workbox to handle the different sources that will make up the streaming response.

```javascript
const cacheStrategy = workbox.strategies.cacheFirst({
  cacheName: workbox.core.cacheNames.precache,
});

const apiStrategy = workbox.strategies.staleWhileRevalidate({
  cacheName: API_CACHE_NAME,
  plugins: [
    new workbox.expiration.Plugin({maxEntries: 50}),
  ],
});
```

- The first strategy reads data that's been precached, like the partial HTML templates.
- The second strategy implements the [stale-while-revalidate](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#stale-while-revalidate) caching logic, along with [least-recently-used cache expiration](https://developer.chrome.com/docs/workbox/modules/workbox-expiration/#restrict-the-number-of-cache-entries) logic once we reach 50 entries.

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:

```javascript
workbox.streams.strategy([
  () => cacheStrategy.handle({
      request: new Request(getCacheKeyForURL("/head.html")),
    }),
  () => cacheStrategy.handle({
      request: new Request(getCacheKeyForURL("/navbar.html")),
    }),
  async ({event, url}) => {
    const tag = url.searchParams.get('tag') || DEFAULT_TAG;
    const listResponse = await apiStrategy.handle(…);
    const data = await listResponse.json();
    return templates.index(tag, data.items);
  },
  () => cacheStrategy.handle({
      request: new Request(getCacheKeyForURL("/foot.html")),
    }),
]);
```

- The first two sources are precached partial templates read directly from the service worker's cache, so they'll always be available immediately.
- The next source function fetches data from the network, and processes the response into the HTML that the web app expects.
- Finally, a cached copy of the footer and closing HTML tags is streamed to complete the response.

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'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.


# Broadcast updates to pages with service workers

In some scenarios the service worker might need to proactively communicate with any of the active
tabs it controls to inform of a certain event. Examples include:

- Informing the page when a new version of the service worker has been installed, so that the page
  can show an **"Update to refresh"** button to the user to access the new functionality
  immediately.
- Letting the user know about a change on cached data that took place on the service worker side, by
  showing an indication, like: **"The app is now ready to work offline"**, or **"New version of the
  content available"**.

<figure>
  

*Diagram showing a service worker communicating with the page to send an update.*

</figure>

We'll call these types of use cases where the service worker doesn't need to receive a message from
the page to start a communication **"broadcast updates"**. In this guide we'll review different
ways of implementing this type of communication between pages and service workers, by using standard
browser APIs and the [Workbox library](https://developer.chrome.com/docs/workbox/).

  Check out [Workers overview](/workers-overview/) for a high-level explanation
  of when to use web workers versus service workers and the rest of the
  [Communicate with workers](/reliable/#communicate-with-workers) series
  for guides on other common use cases.

## Production cases {: #production-cases }

### Tinder {: #tinder }

Tinder PWA uses [`workbox-window`](https://developer.chrome.com/docs/workbox/modules/workbox-window/) to listen to
important service worker lifecycle moments from the page ("installed", "controlled" and
"activated"). That way when a new service worker comes into play, it shows an **"Update Available"**
banner, so that they can refresh the PWA and access the latest features:

<figure>
  

*A screenshot of Tinder's webapp 'Update Available' functionality.*

  <figcaption>In the Tinder PWA, the service worker tells the page that a new version is ready, and the page shows users a "Update Available" banner.</figcaption>
</figure>

### Squoosh {: #squoosh }

In the [Squoosh PWA](https://squoosh.app/), when the service worker has cached all of the necessary
assets to make it work offline, it sends a message to the page to show a "Ready to work offline"
toast, letting the user know about the feature:

<figure>
  

*A screenshot of Squoosh webapp 'Ready to work offline' functionality.*

  <figcaption>In the Squoosh PWA the service worker broadcasts an update to the page when cache is ready, and the page displays "Ready to work offline" toast.
</figcaption>
</figure>

## Using Workbox {: #using-workbox }

### Listen to service worker lifecycle events {: #listen-to-service-worker-lifecycle-events }

`workbox-window` provides a straightforward interface to listen to [important service worker lifecycle
events](https://developer.chrome.com/docs/workbox/modules/workbox-window/#important-service-worker-lifecycle-moments).
Under the hood, the library uses client-side APIs like
[`updatefound`](https://developer.mozilla.org/docs/Web/API/ServiceWorkerRegistration/onupdatefound)
and [statechange](https://developer.mozilla.org/docs/Web/API/ServiceWorker/onstatechange)
and provides higher level event listeners in the `workbox-window` object, making it easier for the
user to consume these events.

The following page code lets you detect every time a new version of the service worker is installed,
so you can communicate it to the user:

```javascript
const wb = new Workbox('/sw.js');

wb.addEventListener('installed', (event) => {
  if (event.isUpdate) {
    // Show "Update App" banner
  }
});

wb.register();
```

### Inform the page of changes in cache data {: #inform-the-page-of-changes-in-cache-data }

The Workbox package
[`workbox-broadcast-update`](https://developer.chrome.com/docs/workbox/modules/workbox-broadcast-update/)
provides a standard way of notifying window clients that a cached response has been updated. This is
most commonly used along with the [StaleWhileRevalidate
strategy](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#stale-while-revalidate).

To broadcast updates add a `broadcastUpdate.BroadcastUpdatePlugin` to your strategy options in the
service worker side:

```javascript
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';

registerRoute(
  ({url}) => url.pathname.startsWith('/api/'),
  new StaleWhileRevalidate({
    plugins: [
      new BroadcastUpdatePlugin(),
    ],
  })
);
```

In your web app, you can listen for these events like so:

```javascript
navigator.serviceWorker.addEventListener('message', async (event) => {
  // Optional: ensure the message came from workbox-broadcast-update
  if (event.data.meta === 'workbox-broadcast-update') {
    const {cacheName, updatedUrl} = event.data.payload;

    // Do something with cacheName and updatedUrl.
    // For example, get the cached content and update
    // the content on the page.
    const cache = await caches.open(cacheName);
    const updatedResponse = await cache.match(updatedUrl);
    const updatedText = await updatedResponse.text();
  }
});
```

## Using browser APIs {: #using-browser-apis }

If the functionality that Workbox provides is not enough for your needs, use the following browser
APIs to implement **"broadcast updates"**:

### Broadcast Channel API {: #broadcast-channel-api }

The service worker creates a [BroadcastChannel
object](https://developer.mozilla.org/docs/Web/API/BroadcastChannel) and starts sending
messages to it. Any context (e.g. page) interested in receiving these messages can instantiate a
`BroadcastChannel` object and implement a message handler to receive messages.

To inform the page when a new service worker is installed, use the following code:

```javascript
// Create Broadcast Channel to send messages to the page
const broadcast = new BroadcastChannel('sw-update-channel');

self.addEventListener('install', function (event) {
  // Inform the page every time a new service worker is installed
  broadcast.postMessage({type: 'CRITICAL_SW_UPDATE'});
});
```

The page listens to these events by subscribing to the `sw-update-channel`:

```javascript
// Create Broadcast Channel and listen to messages sent to it
const broadcast = new BroadcastChannel('sw-update-channel');

broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'CRITICAL_SW_UPDATE') {
    // Show "update to refresh" banner to the user.
  }
};
```

This is a simple technique, but its limitation is browser support: at the moment of this writing,
[Safari doesn't support this API](https://caniuse.com/?search=Broadcastchannel).

### Client API {: #client-api }

The [Client API](https://developer.mozilla.org/docs/Web/API/Client) provides a straightforward
way of communicating with multiple clients from the service worker by iterating over an array of
[`Client`](https://developer.mozilla.org/docs/Web/API/Client) objects.

Use the following service worker code to send a message to the last focused tab:

```javascript
// Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
  if (clients && clients.length) {
    // Respond to last focused tab
    clients[0].postMessage({type: 'MSG_ID'});
  }
});
```

The page implements a message handler to intercept these messages:

```javascript
// Listen to messages
navigator.serviceWorker.onmessage = (event) => {
     if (event.data && event.data.type === 'MSG_ID') {
         // Process response
   }
};
```

Client API is a great option for cases like broadcasting information to multiple active tabs. The
API is supported by all major browsers, but not all of its methods are. Check browser support before
using it.

### Message Channel {: #message-channel }

[Message Channel](https://developer.mozilla.org/docs/Web/API/Channel_Messaging_API) requires
an initial configuration step, by passing a port from the page to the service worker, to establish a
communication channel between them. The page instantiates a `MessageChannel` object and passes a
port to the service worker, via the `postMessage()` interface:

```javascript
const messageChannel = new MessageChannel();

// Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
  messageChannel.port2,
]);
```

The page listens to messages by implementing an "onmessage" handler on that port:

```javascript
// Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
```

The service worker receives the port and saves a reference to it:

```javascript
// Initialize
let communicationPort;

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PORT_INITIALIZATION') {
    communicationPort = event.ports[0];
  }
});
```

From that point it can send messages to the page, by calling `postMessage()` in the reference to the
port:

```javascript
// Communicate
communicationPort.postMessage({type: 'MSG_ID' });
```

`MessageChannel` might be more complex to implement, due to the need of initializing ports, but it's
supported by [all major browsers](https://caniuse.com/?search=channel%20messaging).

## Next steps {: #next-steps }

In this guide we explored one particular case of Window to service worker communication:
**"broadcast updates"**. The examples explored include listening to important service worker
lifecycle events, and communicating to the page about changes in content or cached data. You can think
of more interesting use cases where the service worker proactively communicates with the page,
without receiving any message previously.

For more patterns of Window and service worker communication check out:

- [Imperative caching guide](/imperative-caching-guide): Calling a service worker from the page to
  cache resources in advance (e.g. in prefetching scenarios).
- [Two-way communication](/two-way-communication-guide): Delegating a task to a service worker (e.g.
  a heavy download), and keeping the page informed on the progress.

## Additional resources {: #additional-resources }

- [workbox-window](https://developer.chrome.com/docs/workbox/modules/workbox-window/)
- [workbox-broadcast-update](https://developer.chrome.com/docs/workbox/modules/workbox-broadcast-update/)
- [Workbox 4: Implementing refresh-to-update-version flow using the workbox-window
  module](https://medium.com/google-developer-experts/workbox-4-implementing-refresh-to-update-version-flow-using-the-workbox-window-module-41284967e79c)


# Building resilient search experiences with Workbox

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.

This codelab uses [Chrome DevTools](https://www.google.com/chrome/). Download Chrome if you don't already have it.

## Measure

Before adding optimizations, it's always a good idea to first analyze the current state of the application.

In the new tab that just opened, check how the website behaves when going offline:

1. Open Chrome DevTools and select the Network panel.
1. In the [Throttling drop-down list](https://developer.chrome.com/docs/devtools/network/reference/#throttling), select **Offline**.
1. In the demo app enter a search query, then click the **Search** button.

The standard browser error page is shown:

*A screenshot of the default offline UX in the browser.*

## Provide a fallback response

The service worker contains the code to add the offline page to the [precache list](https://developer.chrome.com/docs/workbox/modules/workbox-precaching/#explanation-of-the-precache-list), so it can always be cached at the service worker `install` event.

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. [webpack](https://webpack.js.org/) or [gulp](https://gulpjs.com/)).

For simplicity, we've already done it for you. The following code at `public/sw.js` does that:

```javascript
const FALLBACK_HTML_URL = '/index_offline.html';
…
workbox.precaching.precacheAndRoute([FALLBACK_HTML_URL]);
```


To learn more about how to integrate Workbox with build tools, check out the [webpack Workbox plugin](https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/) and the [Gulp Workbox plugin](https://developers.google.com/codelabs/workbox-lab#0).

Next, add code to use the offline page as a fallback response:

1. Add the following code to the bottom of `public/sw.js`:

```javascript
workbox.routing.setDefaultHandler(new workbox.strategies.NetworkOnly());

workbox.routing.setCatchHandler(({event}) => {
  switch (event.request.destination) {
    case 'document':
      return caches.match(FALLBACK_HTML_URL);
      break;
    default:
      return Response.error();
  }
});
```


  The Glitch UI says `workbox is not defined` because it doesn't realize that the
  `importScripts()` call on line 1 is importing the library.

The code does the following:

- Defines a default [Network Only strategy](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#network-only) that will apply to all requests.
- Declares a global error handler, by calling `workbox.routing.setCatchHandler()` to manage failed requests. When requests are for documents, a fallback offline HTML page will be returned.

To test this functionality:

1. Go back to the other tab that is running your app.
1. Set the **Throttling** drop-down list back to **Online**.
1. Press Chrome's **Back** button to navigate back to the search page.
1. Make sure that the **Disable cache** checkbox in DevTools is disabled.
1. Long-press Chrome's **Reload** button and select
   [**Empty cache and hard reload**](https://stackoverflow.com/q/14969315/1669860)
   to ensure that your service worker is updated.
1. Set the **Throttling** drop-down list back to **Offline** again.
1. Enter a search query, and click the **Search** button again.

The fallback HTML page is shown:

*A screenshot of the custom offline UX in the browser.*

## Request notification permission

For simplicity, the offline page at `views/index_offline.html` already contains the code to request notification permissions in a script block at the bottom:

```javascript
function requestNotificationPermission(event) {
  event.preventDefault();

  Notification.requestPermission().then(function (result) {
    showOfflineText(result);
  });
}
```

The code does the following:

- When the user clicks **subscribe to notifications** the `requestNotificationPermission()` function is called, which calls `Notification.requestPermission()`, to show the default browser permission prompt. The promise resolves with the permission picked by the user, which can be either `granted`, `denied`, or `default`.
- Passes the resolved permission to  `showOfflineText()` to show the appropriate text to the user.

## Persist offline queries and retry when back online

Next, implement [Workbox Background Sync](https://developer.chrome.com/docs/workbox/modules/workbox-background-sync/) to persist offline queries, so they can be retried when the browser detects that connectivity has returned.

1. Open `public/sw.js` for edit.
1. Add the following code at the end of the file:

```javascript
const bgSyncPlugin = new workbox.backgroundSync.Plugin('offlineQueryQueue', {
  maxRetentionTime: 60,
  onSync: async ({queue}) => {
    let entry;
    while ((entry = await queue.shiftRequest())) {
      try {
        const response = await fetch(entry.request);
        const cache = await caches.open('offline-search-responses');
        const offlineUrl = `${entry.request.url}&notification=true`;
        cache.put(offlineUrl, response);
        showNotification(offlineUrl);
      } catch (error) {
        await this.unshiftRequest(entry);
        throw error;
      }
    }
  },
});
```

The code does the following:

- `workbox.backgroundSync.Plugin` contains the logic to add failed requests to a queue so they can be retried later. These requests will be persisted in [IndexedDB](https://developer.mozilla.org/docs/Web/API/IndexedDB_API).
- `maxRetentionTime` indicates the amount of time a request may be retried. In this case we have chosen 60 minutes (after which it will be discarded).
- `onSync` 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.
- The network response is added to the `offline-search-responses` cache, appending the `&notification=true` query param, so that this cache entry can be picked up when a user clicks on the notification.

To integrate background sync with your service, define a [NetworkOnly](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#network-only) strategy for requests to the search URL (`/search_action`) and pass the previously defined `bgSyncPlugin`. Add the following code to the bottom of `public/sw.js`:

```javascript
const matchSearchUrl = ({url}) => {
  const notificationParam = url.searchParams.get('notification');
  return url.pathname === '/search_action' && !(notificationParam === 'true');
};

workbox.routing.registerRoute(
  matchSearchUrl,
  new workbox.strategies.NetworkOnly({
    plugins: [bgSyncPlugin],
  }),
);
```

This tells Workbox to always go to the network, and, when requests fail, use the background sync logic.

Next, add the following code to the bottom of `public/sw.js` to define a caching strategy for requests coming from notifications. Use a [CacheFirst](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#cache-first-cache-falling-back-to-network) strategy, so they can be served from the cache.

```javascript
const matchNotificationUrl = ({url}) => {
  const notificationParam = url.searchParams.get('notification');
  return (url.pathname === '/search_action' && (notificationParam === 'true'));
};

workbox.routing.registerRoute(matchNotificationUrl,
  new workbox.strategies.CacheFirst({
     cacheName: 'offline-search-responses',
  })
);
```

Finally, add the code to show notifications:

```javascript
function showNotification(notificationUrl) {
  if (Notification.permission) {
     self.registration.showNotification('Your search is ready!', {
        body: 'Click to see you search result',
        icon: '/img/workbox.jpg',
        data: {
           url: notificationUrl
        }
     });
  }
}

self.addEventListener('notificationclick', function(event) {
  event.notification.close();
  event.waitUntil(
     clients.openWindow(event.notification.data.url)
  );
});
```

## Test the feature

1. Go back to the other tab that is running your app.
1. Set the **Throttling** drop-down list back to **Online**.
1. Press Chrome's **Back** button to navigate back to the search page.
1. Long-press Chrome's **Reload** button and select
   [**Empty cache and hard reload**](https://stackoverflow.com/q/14969315/1669860)
   to ensure that your service worker is updated.
1. Set the **Throttling** drop-down list back to **Offline** again.
1. Enter a search query, and click the **Search** button again.
1. Click **subscribe to notifications**.
1. When Chrome asks you if you want to grant the app permission to send notifications,
   click **Allow**.
1. Enter another search query and click the **Search** button again.
1. Set the **Throttling** drop-down list back to **Online** again.

Once the connection is back a notification will be shown:

*A screenshot of the full offline flow.*

## Conclusion

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.


# Explore DevTools Network panel



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'll have the skills you need to figure out _what_ your own web application is
loading and _when_ it's making each request.

The screenshots and instructions in this codelab assume that you're using
Chrome. Each browser has its own DevTools experience, which might not match what
you see in this codelab.

## Navigate to the Network Panel

Navigate to the Network panel to see the network traffic for the demo
application.

1. Reload the page to see the network traffic.

The Network panel shows all the assets loaded because of your initial
navigation:

*Chrome DevTools' network panel.*

The actual columns you see in the Network panel may be different; the
screenshot shows a simplified view with everything but the **Name**, **Type**, and
**Waterfall** columns hidden.

## How to interpret the entries

Each row of recorded network traffic represents a single request and response
pair.

The first row, with type `document`, is the initial navigation request for the
web app'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.

The second and third rows, showing a CSS `stylesheet` and a `script` subresource
being loaded, are dependent requests that were initiated by the main document.

Looking at _when_ those requests are made, the waterfall diagram shows that
they're not started until very late in the process of responding to the
navigation request.

Taken together, the requests for the HTML document, CSS, and
JavaScript are needed to display the full page during the
initial navigation.

## Create some additional runtime requests

With the **Network** panel still open and recording, it'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.

Trigger these additional requests by clicking **Find Me** in the app and then
**Allow** in the popup that appears.
This will allow the site to access your current location:

*The allow location permission prompt.*

You could also deny geolocation permission, in which case the web app
will fall back to a default location.

Once the web app has a location to work with, clicking **Find Nearby
Wikipedia Entries** results in several additional network requests. You
should see something like this:

*image*

## Interpret the new entries

As before, each row of recorded network traffic represents a single request
and response pair.

The first row of the new entries represents a request with a type of `fetch`,
which corresponds to the
[way the web app requests data](https://developer.mozilla.org/docs/Web/API/Fetch_API)
from the Wikipedia API.

The following rows all are images (`png` or `jpeg`) associated with the
Wikipedia entries. Although it's a little hard to see from the screenshot, their
entries in the Waterfall column directly flow from the API response.

For all of these additional requests, the _when_ is going to vary based on how
long you've had the page open before you click on **Find Nearby Wikipedia
Entries**. Most important here is that the _when_ 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.

Requests made after a gap of time following a navigation fall into the category
of "runtime requests," as opposed to the initial set of requests used to display
the page when you first navigated to it.

## Summing things up

Having gone through the steps in this codelab, you're now familiar with the
tools you can use to analyze what _any_ web application loads.

The Network panel helps you answer the question of _what_'s being loaded, via
the URLs in the Name column and the data in the Type column, along with _when_
it's being loaded, via the waterfall display.

You've also seen that requests made by a web page can (usually) be grouped into
one of two categories:

1. Initial requests, made right after navigating to a new page, for the
    HTML, JavaScript, CSS (and potentially other assets).
1. 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.


# Configuring HTTP caching behavior



This codelab shows you how to change the HTTP caching headers returned by a
Node.js-based web server, running the [Express](https://expressjs.com/) 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's DevTools.

While the specific instructions are tailored towards Express, the general
principles about choosing the correct caching headers apply to any web server
environment.

## Get familiar with the sample project

These are the key files you will be working with in the sample project:

+  `server.js` contains the Node.js code that serves the web app's
    content. It uses [Express](https://expressjs.com/) to handle HTTP requests
    and responses. In particular, `express.static()` is used to serve all of
    the local files in the public directory, so the `serve-static`
    [documentation](https://expressjs.com/en/resources/middleware/serve-static.html)
    will come in handy.
+  `public/index.html` is the web app's HTML. Like most HTML files, it does not
    contain any versioning information as part of its URL.
+  `public/app.15261a07.js` and `public/style.391484cf.css` are the web app's JavaScript
    and CSS assets. These files each contain a hash in their URLs,
    corresponding to their contents. The `index.html` is responsible for keeping
    track of which specific versioned URL to load.

In the "real world", 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
[webpack](https://webpack.js.org/guides/caching/#output-filenames).
For the purposes of this codelab, assume that the hashes were generated as part
of a build process that already took place.

## Configure caching headers for our HTML

When responding to requests for URLs that don't contain versioning info, make
sure you add `Cache-Control: no-cache` to your response messages. Along with
that, setting one of two additional response headers is recommended: either
[`Last-Modified`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Last-Modified)
or [`ETag`](https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag). The
`index.html` falls into this category. You can break this down into two steps.

First, the `Last-Modified` and `ETag` headers are controlled by the
[`etag`](https://expressjs.com/en/resources/middleware/serve-static.html#etag)
and
[`lastModified`](https://expressjs.com/en/resources/middleware/serve-static.html#lastmodified)
configuration options. Both of these options actually default to `true` for all
HTTP responses, so in this current setup, you don't _have_ to opt-in to get that
behavior. But you can be explicit in your configuration anyway.

Second, you need to be able to add in the `Cache-Control: no-cache` header, but
only for your HTML documents (`index.html`, in this case). The easiest way to
conditionally set this header is to write a custom
[`setHeaders function`](https://expressjs.com/en/resources/middleware/serve-static.html#setheaders),
and within that, check to see if the incoming request is for an HTML document.

The static serving configuration in `server.js` starts out as this:

```js
app.use(express.static('public'));
```

- Make the changes described above, and you should end up with something that
looks like:

```js
app.use(express.static('public', {
  etag: true, // Just being explicit about the default.
  lastModified: true,  // Just being explicit about the default.
  setHeaders: (res, path) => {
    if (path.endsWith('.html')) {
      // All of the project's HTML files end in .html
      res.setHeader('Cache-Control', 'no-cache');
    }
  },
}));
```

## Configure caching headers for the versioned URLs

When responding to requests for URLs that contain
"[fingerprint](https://en.wikipedia.org/wiki/Fingerprint_(computing))" or
versioning information, and whose contents are never meant to change, add
`Cache-Control: max-age=31536000` to your responses. The `app.15261a07.js` and
`style.391484cf.css` fall into this category.

Building off the
[`setHeaders function`](https://expressjs.com/en/resources/middleware/serve-static.html#setheaders)
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 `Cache-Control:
max-age=31536000` header.

The most robust way of doing this is to use a
[regular expression](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Regular_Expressions)
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's always eight
characters from the set of digits 0–9 and the lowercase letters a–f (i.e.
[hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) characters). The hash
is always separated by a `.` character on either side.

A regular expression that
[matches those general rules](https://jex.im/regulex/#!flags=&re=%5C.%5B0-9a-f%5D%7B8%7D%5C.)
can be expressed as `new RegExp('\\.[0-9a-f]{8}\\.')`.

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 `.js` or `.css` 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.

- Modify the `setHeaders` function so it looks like this:

```js
app.use(express.static('public', {
  etag: true, // Just being explicit about the default.
  lastModified: true,  // Just being explicit about the default.
  setHeaders: (res, path) => {
    const hashRegExp = new RegExp('\\.[0-9a-f]{8}\\.');

    if (path.endsWith('.html')) {
      // All of the project's HTML files end in .html
      res.setHeader('Cache-Control', 'no-cache');
    } else if (hashRegExp.test(path)) {
      // If the RegExp matched, then we have a versioned URL.
      res.setHeader('Cache-Control', 'max-age=31536000');
    }
  },
}));
```

## Confirm the new behavior using DevTools

You can get familiar with the Network panel in Chrome's DevTools by
working through [this codelab](/codelab-explore-network-panel).

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.

- 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:

*Configuring DevTools' Network panel.*

Here, the columns to pay attention to are `Name`, `Status`, `Cache-Control`,
`ETag`, and `Last-Modified`.

- With the DevTools open to the Network panel, refresh the page.

After the page has loaded, you should see entries in the Network panel that look
like the following:

*Network panel columns.*

The first row is for the HTML document that you navigated to. This is properly
served with `Cache-Control: no-cache`. The HTTP response status for that request
is a [`304`](https://developer.mozilla.org/docs/Web/HTTP/Status/304). 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 `Last-Modified` and `ETag`
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.

`Cache-Control: no-cache` doesn't mean "never used the cached copy". It
means "always check with the server first, and use the cached copy if there's a
HTTP 304 response."

The next two rows are for the versioned JavaScript and CSS assets. You should
see them served with `Cache-Control: max-age=31536000`, and the HTTP status for
each is [`200`](https://developer.mozilla.org/docs/Web/HTTP/Status/200).
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 "(from disk cache)".

*A network response status of 200.*

The actual values for the ETag and Last-Modified columns don't matter much. The
important thing is to confirm that they're being set.

## Summing things up

Having gone through the steps in this codelab, you'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's DevTools.


# Working with service workers



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.

## Get familiar with the sample project

The files in the sample project most relevant to this codelab are:

+  `register-sw.js` starts out empty, but it will contain the code used
    to register the service worker. It's already being loaded via a `<script>`
    tag inside of the project's `index.html`.
+  `service-worker.js` is similarly empty. It's the file that will contain
    the service worker for this project.

## Add in the service worker registration code

A service worker (even an empty one, like the current `service-worker.js` file)
won't be used unless it's
[registered](https://developer.mozilla.org/docs/Web/API/ServiceWorkerContainer/register)
first. You can do this via a call to:

```js
navigator.serviceWorker.register(
  '/service-worker.js'
)
```

inside your `register-sw.js` file.

Before you add that code, though, there are a couple of points to take into
account.

First, not every browser
[supports](https://developer.mozilla.org/docs/Web/API/Service_Worker_API#Browser_compatibility)
service workers. This is particularly true for older versions of browsers that
don't automatically update. So it's a best practice to call
`navigator.serviceWorker.register()` conditionally, after checking whether
`navigator.serviceWorker` is supported.

Second, when you register a service worker, the browser runs the code in your
`service-worker.js` file, and may potentially start downloading URLs to populate
caches, depending on the code in your service worker's
[`install`](/service-worker-lifecycle/#install)
and
[`activate`](/service-worker-lifecycle/#activate)
event handlers.

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'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
`window.load` event has been fired.

Waiting for <code>window.load</code> is a one-size-fits-all solution. If you know
more about how your web page loads resources, or if you're using a web app
framework that supports its own "everything's ready" lifecycle event, you can
tweak the timing accordingly.

Putting those two points together, add this general-purpose service worker
registration code to your `register-sw.js` file:

```js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js');
  });
}
```

## Add some service worker logging code

Your `service-worker.js` file is where all the logic for your service worker
implementation would normally go. You'd use a mix of the service worker
[lifecycle events](/service-worker-lifecycle/),
the
[Cache Storage API](https://developer.mozilla.org/docs/Web/API/CacheStorage),
and knowledge about your web app's network traffic to create a perfectly crafted
service worker, ready to handle all of your web app's requests.

But… that's all for learning later. At this stage, the focus is on observing
various service worker events, and getting comfortable using Chrome's DevTools
to debug the state of your service worker.

To that end, add in the following code to `service-worker.js`, which will log
messages to the DevTools console in response to various events (but not do much
else):

```js
self.addEventListener('install', (event) => {
  console.log('Inside the install handler:', event);
});

self.addEventListener('activate', (event) => {
  console.log('Inside the activate handler:', event);
});

self.addEventListener(fetch, (event) => {
  console.log('Inside the fetch handler:', event);
});
```

## Get familiar with the Service Workers panel in DevTools

Now that you've got the code added to `register-sw.js` and `service-worker.js`
files, it's time to visit the Live version of your sample project, and observe
the service worker in action.

You should see something like the following log messages,
showing that the service worker has been installed and activated:

*Shows service worker is installed and activated.*

Then visit the **Applications** tab, and select the **Service Workers** panel.
You should see something like the following:

*Shows service worker details in service worker panel.*

This lets you know that there's a service worker with a source URL of
`service-worker.js`, for the web app `solar-donkey.glitch.me`, that's currently
activated and running. It also tells you that there's currently one client (open
tab) that's being controlled by the service worker.

You can use the links on this panel, like `Unregister`, or `stop`, to make
changes to the currently registered service worker for debugging purposes.

Service workers tend to stick around, unless they're explicitly unregistered. If
you find yourself wanting to "start fresh" during local development, a great way
of doing so is to use a [Chrome Incognito
window](https://support.google.com/chrome/answer/95464) 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.

## Trigger the service worker update flow

One of the key concepts to understand when developing with service workers is
the idea of
[an update flow](/service-worker-lifecycle/#updates).

After your users visit a web app which registers a service worker, they'll end
up with the code for the current copy of `service-worker.js` installed on their
local browser. But what happens when you make updates to the version of
service-worker.js that's stored on your web server?

When a repeat visitor returns to a URL that's within the scope of a service worker,
the browser will automatically request the latest `service-worker.js` 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.

You can simulate this update flow by going back to the code editor for your project, and making _any_ change to the code. One quick change would be
to replace

```js
self.addEventListener('install', (event) => {
  console.log('Inside the install handler:', event);
});
```

with

```js
self.addEventListener('install', (event) => {
  console.log('Inside the UPDATED install handler:', event);
});
```

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:

*Shows two versions of service worker installed.*

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's in the
[`waiting` state](/service-worker-lifecycle/#waiting),
and will remain waiting until all of the open tabs that are controlled by the
old service worker are closed.

This default behavior ensures that if your new
service worker has a fundamental difference in behavior from your old one—like a
`fetch` handler which responds with resources that are incompatible with older
versions of your web app—it won't go into effect until a user has shut down all
previous instances of your web app.

If you don't want to wait, it's possible to
[call `skipWaiting()`](/service-worker-lifecycle/#skip-the-waiting-phase)
inside of your service worker (usually in the `install` handler), or to simulate
that behave by clicking on the `skipWaiting` link in DevTools.

## Summing things up

You should now be comfortable with the process of registering a service worker
and observing a service worker's behavior using Chrome's DevTools.

You'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.


# Handling navigation requests

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's possible with [HTTP
caching](/http-cache/).

As detailed in the [Identify resources loaded from the
network](/identify-resources-via-network-panel/) guide, a navigation request is the first of
potentially many requests made in the
["waterfall"](https://developer.chrome.com/docs/devtools/network/reference/#waterfall)
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.

Inside of a service worker's `fetch` event handler, you can determine whether a request is a
navigation by checking the `request.mode` property on the `FetchEvent`. If it's set to `'navigate'`,
then it's a navigation request.

As a general rule, do not use long-lived <code>[Cache-Control headers](/http-cache/)</code> to cache
the HTML response for a navigation request. They should normally be satisfied via the network, with
<code>Cache-Control: no-cache</code>, 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 <em>might</em> be slow. At the very least, it
means that it won't be <em>reliably</em> fast.

`Cache-Control: no-cache` means the browser must check (or "revalidate") with the server before
using a previously cached resource. This requires a round-trip network communication to complete
before the resource can be used.

## Different approaches for architectures

Figuring out _how_ to respond to navigation requests while avoiding the network can be tricky. The
right approach depends very much on your web site's architecture and the number of unique URLs that
users might navigate to.

While there's no one-size-fits all solution, the following general guidelines should help you decide
which approach is the most viable.

### Small static sites

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.

Using [precaching](/precache-with-workbox/), 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.

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
[stale-while-revalidate](/runtime-caching-with-workbox/#stale-while-revalidate) 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 **frequently** by the same set of users, and if you feel comfortable about
those URLs being revalidated independently of each other.

### Single-page apps

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 [History
API](https://developer.mozilla.org/docs/Web/API/History_API) to modify the current URL as the
user interacts with the web app, leading to what's effectively a "simulated" navigation. While
subsequent navigations might be "fake", the initial navigation is real, and it's still important to
make sure that it isn't blocked on the network.

Fortunately, if you're using the single-page architecture, there's a straightforward pattern to
follow for serving the initial navigation from the cache: the [application
shell](/learn/pwa/architecture/). 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 [skeleton
content](https://css-tricks.com/building-skeleton-screens-css-custom-properties/). 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.

Workbox provides the tools that you need to implement this approach; the <code>[navigateFallback
option](https://developer.chrome.com/docs/workbox/reference/workbox-build/#method-generateSW)</code>
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.

### Multi-page apps

If your web server generates your site's HTML dynamically, or if you have more than a few dozen
unique pages,  then it's much harder to avoid the network when handling navigation requests. The
advice in [Everything else](#everything-else) will likely apply to you.

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
[Node.js](https://nodejs.org)-specific features, like file system access).

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 [Beyond SPAs:
alternative architectures for your
PWA](https://developer.chrome.com/blog/beyond-spa/) can get you started.

### Everything else {: #everything-else }

If you can'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'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 [Building Faster, More Resilient
Apps with Service Worker](https://youtu.be/25aCD5XL1Jk)). You can mitigate this overhead by enabling
a feature called [navigation
preload](https://developer.chrome.com/blog/navigation-preload/), and then [using the
network
response](https://developer.chrome.com/blog/navigation-preload/#using-the-preloaded-response)
that's been preloaded inside of your `fetch` event handler.

Workbox [provides a helper
library](https://developer.chrome.com/docs/workbox/modules/workbox-navigation-preload/) that
feature-detects whether navigation preload is supported, and if so, simplifies the process of
telling your service worker to use the network response.

_<span>Photo by <a href="https://unsplash.com/@aaronburden?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Aaron Burden</a> on <a href="https://unsplash.com/s/photos/navigate?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>,_


# Prevent unnecessary network requests with the HTTP Cache

Fetching resources over the network is both slow and expensive:

* Large responses require many roundtrips between the browser and the server.
* Your page won't load until all of its [critical resources][crp] have downloaded completely.
* If a person is accessing your site with a limited mobile data plan, every unnecessary
  network request is a waste of their money.

How can you avoid unnecessary network requests? The browser's HTTP Cache is your
first line of defense. It's not necessarily the most powerful or flexible
approach, and you have limited control over the lifetime of cached responses,
but it's effective, it's supported in all browsers, and it doesn't require much
work.

This guide shows you the basics of an effective HTTP caching implementation.

## Browser compatibility {: #browser-compatibility }

There isn't actually a single API called the HTTP Cache. It's the general name
for a collection of web platform APIs. Those APIs are supported in all browsers:

* [`Cache-Control`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control#Browser_compatibility)
* [`ETag`](https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag#Browser_compatibility)
* [`Last-Modified`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Last-Modified#Browser_compatibility)

## How the HTTP Cache works {: #overview }

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's a match, the response is read from the cache, which
eliminates both the network latency and the data costs that the transfer incurs.

The HTTP Cache's behavior is controlled by a combination of
[request headers](https://developer.mozilla.org/docs/Glossary/Request_header) and
[response headers](https://developer.mozilla.org/docs/Glossary/Response_header).
In an ideal scenario, you'll have control over both the code for your
web application (which will determine the request headers) and your web server's
configuration (which will determine the response headers).

Check out MDN's [HTTP Caching](https://developer.mozilla.org/docs/Web/HTTP/Caching) article
for a more in-depth conceptual overview.

## Request headers: stick with the defaults (usually) {: #request-headers }

While there are a number of important headers that should be included in your
web app'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 [`If-None-Match`][if-none-match] and
[`If-Modified-Since`][if-modified-since] just appear based on the browser's
understanding of the current values in the HTTP Cache.

This is good news—it means that you can continue including tags like `<img
src="my-image.png">` in your HTML, and the browser  automatically takes care of
HTTP caching for you, without extra effort.

Developers who do need more control over the HTTP Cache in their web application
have an alternative—you can "drop down" a level, and manually use the [Fetch
API](https://developer.mozilla.org/docs/Web/API/Fetch_API), passing it
[`Request`](https://developer.mozilla.org/docs/Web/API/Request) objects
with specific
[`cache`](https://developer.mozilla.org/docs/Web/API/Request/cache)
overrides set. That's beyond the scope of this guide, though!

## Response headers: configure your web server {: #response-headers }

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:

* [`Cache-Control`][cache-control].
  The server can return a `Cache-Control` directive to specify how, and for how
  long, the browser and other intermediate caches should cache the individual
  response.
* [`ETag`][etag]. When
  the browser finds an expired cached response, it can send a small token
  (usually a hash of the file'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's
  no need to re-download it.
* [`Last-Modified`][last-modified].
  This header serves the same purpose as `ETag`, but uses a time-based strategy
  to determine if a resource has changed, as opposed to the content-based strategy
  of `ETag`.

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 _how_ to configure headers varies greatly
depending on which web server you use, and you should consult your server's
documentation to get the most accurate details.

To save you some searching, here are instructions on configuring a few popular
web servers:

+  [Express](https://expressjs.com/en/api.html#express.static)
+  [Apache](https://httpd.apache.org/docs/2.4/caching.html)
+  [nginx](http://nginx.org/en/docs/http/ngx_http_headers_module.html)
+  [Firebase Hosting](https://firebase.google.com/docs/hosting/full-config)
+  [Netlify](https://www.netlify.com/blog/2017/02/23/better-living-through-caching/)

Leaving out the `Cache-Control` response header does not disable HTTP caching!
Instead, browsers [effectively
guess](https://www.mnot.net/blog/2017/03/16/browser-caching#heuristic-freshness)
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.

## Which response header values should you use? {: #response-header-strategies }

There are two important scenarios that you should cover when configuring your
web server's response headers.

### Long-lived caching for versioned URLs {: #versioned-urls }

  
    How versioned URLs can help your caching strategy
    Versioned URLs are a good practice because they make it easier to invalidate
    cached responses.
  
  Suppose your server instructs browsers to cache a CSS file
  for 1 year (<code>Cache-Control: max-age=31536000</code>) but your designer just made an
  emergency update that you need to roll out immediately. How do you notify browsers
  to update the "stale" cached copy of the file?
  You can't, at least not without changing the URL of the resource. After the
  browser caches the response, the cached version is used until it's no longer
  fresh, as determined by <code>max-age</code> or <code>expires</code>, 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, <code>style.x234dff.css</code>.

When responding to requests for URLs that contain
"[fingerprint](https://en.wikipedia.org/wiki/Fingerprint_(computing))" or
versioning information, and whose contents are never meant to change, add
`Cache-Control: max-age=31536000` to your responses.

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's great—you've
immediately gained the reliability and speed that comes from avoiding the
network!

Build tools like webpack can
[automate the process](https://webpack.js.org/guides/caching/#output-filenames)
of assigning hash fingerprints to your asset URLs.

You can also add the [`immutable`
property](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control#Revalidation_and_reloading)
to your `Cache-Control` header as a further optimization, though it [will be
ignored](https://www.keycdn.com/blog/cache-control-immutable#browser-support) in
some browsers.

### Server revalidation for unversioned URLs {: #unversioned-urls }

Unfortunately, not all of the URLs you load are versioned. Maybe you're not able
to include a build step prior to deploying your web app, so you can'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
`https://example.com/index.34def12.html`. So what can you do for those URLs?

This is one scenario in which you need to admit defeat. HTTP caching alone isn't
powerful enough to avoid the network completely. (Don't worry—you'll soon learn
about [service workers](/service-workers-cache-storage/), 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.

The following `Cache-Control` values can help you fine-tune where and how unversioned URLs
are cached:

* `no-cache`. This instructs the browser that it must revalidate with the
  server every time before using a cached version of the URL.
* `no-store`. This instructs the browser and other intermediate caches (like CDNs) to never
  store any version of the file.
* `private`. Browsers can cache the file but intermediate caches cannot.
* `public`. The response can be stored by any cache.

Check out [Appendix: `Cache-Control` flowchart](#flowchart) to visualize the process
of deciding which `Cache-Control` value(s) to use. Note also that `Cache-Control` can
accept a comma-separated list of directives. See [Appendix: `Cache-Control` examples](#examples).

Along with that, setting one of two additional response headers can also help:
either [`ETag`][etag] or [`Last-Modified`][last-modified]. As mentioned in
[Response headers](#response-headers), `ETag` and `Last-Modified` both serve the
same purpose: determining if the browser needs to re-download a cached file
that has expired. `ETag` is the recommended approach because it's more accurate.

  
    ETag example
  
  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'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's inefficient because if the resource hasn't changed,
  then there's no reason to download the same information that's already in the
  cache! That's the problem that validation tokens, as specified in the <code>ETag</code>
  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'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't changed and the browser can skip the
  download.

By setting `ETag` or `Last-Modified`, you'll end up making the
revalidation request much more efficient. They end up triggering the
[`If-Modified-Since`][if-modified-since] or [`If-None-Match`][if-none-match]
request headers that were mentioned in [Request headers](#request-headers).

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's a match,
then the server can respond with a [`304 Not Modified`][304] HTTP response,
which is the equivalent of "Hey, keep using what you've already got!" There's
very little data to transfer when sending this type of response, so it's usually
much faster than having to actually send back a copy of the actual resource
being requested.

<figure>
  

*A diagram of a client requesting a resource and the server responding with a 304 header.*

  <figcaption>
    The browser requests <code>/file</code> from the server and includes the <code>If-None-Match</code>
    header to instruct the server to only return the full file if the <code>ETag</code> of
    the file on the server doesn't match the browser's <code>If-None-Match</code> value. In this
    case, the 2 values did match, so the server returns a <code>304 Not Modified</code> response
    with instructions on how much longer the file should be cached (<code>Cache-Control: max-age=120</code>).
  </figcaption>
</figure>

## Summary {: #summary }

The HTTP Cache is an effective way to improve load performance because
it reduces unnecessary network requests. It's supported in all browsers and doesn't
take too much work to set up.

The following `Cache-Control` configurations are a good start:

* `Cache-Control: no-cache` for resources that should be revalidated with the server before every use.
* `Cache-Control: no-store` for resources that should never be cached.
* `Cache-Control: max-age=31536000` for versioned resources.

And the `ETag` or `Last-Modified` header can help you revalidate expired cache resources more efficiently.

  Try the [HTTP Cache codelab](/codelab-http-cache) to get hands-on experience with `Cache-Control`
  and `ETag` in Express.

## Learn more {: #learn-more }

If you're looking to go beyond the basics of using the `Cache-Control` header,
check out Jake Archibald's [Caching best practices & max-age
gotchas](https://jakearchibald.com/2016/caching-best-practices/) guide.

See [Love your cache](/love-your-cache) for guidance on how to optimize
your cache usage for return visitors.

## Appendix: More tips {: #tips }

If you have more time, here are further ways that you can optimize your usage of the HTTP Cache:

* Use consistent URLs. If you serve the same content on different URLs, then
  that content will be fetched and stored multiple times.
* 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't change often.
* Check out the new [`stale-while-revalidate`](/stale-while-revalidate/) directive if
  some degree of staleness is acceptable in your `Cache-Control` policy.

## Appendix: `Cache-Control` flowchart {: #flowchart }

*Flowchart*

## Appendix: `Cache-Control` examples {: #examples }

<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th><code>Cache-Control</code> value</th>
        <th>Explanation</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code>max-age=86400</code></td>
        <td>
          The response can be cached by browsers and intermediary caches for
          up to 1 day (60 seconds x 60 minutes x 24 hours).
        </td>
      </tr>
      <tr>
        <td><code>private, max-age=600</code></td>
        <td>
          The response can be cached by the browser (but not intermediary caches) for up to 10
          minutes (60 seconds x 10 minutes).
        </td>
      </tr>
      <tr>
        <td><code>public, max-age=31536000</code></td>
        <td>
          The response can be stored by any cache for 1 year.
        </td>
      </tr>
      <tr>
        <td><code>no-store</code></td>
        <td>
          The response is not allowed to be cached and must be fetched in full on every request.
        </td>
      </tr>
    </tbody>
  </table>
</div>

[crp]: /critical-rendering-path/
[304]: https://developer.mozilla.org/docs/Web/HTTP/Status/304
[if-modified-since]: https://developer.mozilla.org/docs/Web/HTTP/Headers/If-Modified-Since
[if-none-match]: https://developer.mozilla.org/docs/Web/HTTP/Headers/If-None-Match
[etag]: https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag
[last-modified]: https://developer.mozilla.org/docs/Web/HTTP/Headers/Last-Modified
[cache-control]: https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control


# Identify resources loaded from the network

The Network panel in your browser'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.

<figure>
  

*Chrome DevTools' network panel.*

</figure>

[Interpret network traffic using Chrome's DevTools](/codelab-explore-network-panel).

This guide uses screenshots and descriptions based on Chrome's DevTools
interface. Other browsers support similar functionality, but the overall
interface will be different if you're not using Chrome.

## Know what you load

Coming up with the right caching strategies for your web application requires
getting a handle on _what_ exactly you'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's vulnerabilities if you hope to cope with them in your
app.

You might think that you already have a pretty good idea as to what your web
application loads. If you'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.

A caching strategy that makes sense for a few small CSS files probably won't
make sense for hundreds of large images, for instance.

## Know when you load

Another part of the overall loading picture is _when_ everything gets loaded.

Some requests to the network, such as the
[navigation request](https://developer.mozilla.org/docs/Web/API/Request/mode#Value)
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.

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've identified as being outside the
critical loading path.

Advanced techniques outside the scope of this guide, like [<link
rel="preload">](https://developer.mozilla.org/docs/Web/HTML/Preloading_content),
add a twist to this story by giving a head start to what would otherwise be a
late-loaded request.

### The Name and Type columns help with the what

The Name and Type columns help provide a meaningful picture of _what_'s being
loaded. The answer to "_what_'s loading?" in the example above is a total of
four URLs, each representing a unique type of content.

<figure>
  

*Chrome DevTools' network panel showing four files loading.*

</figure>

The Name represents the URL that your browser requested—though you'll only see
the last portion of the URL's path listed. For example, if
`https://example.com/main.css` is loaded, you'd only end up seeing `main.css`
listed under Name.

The last few characters of the URL's path, following the
period (e.g. "css"), are known as the URL's extension.
The URL'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,
`v2.html` is a document, and `main.css` is a stylesheet.

### The Waterfall column helps with the when

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's made as
part of the critical loading path and a request that's fired off dynamically,
long after the page's initial load is complete?

The first request in the waterfall is always going to be for the HTML document,
for example, `v2.html`. 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.

*Chrome DevTools' waterfall view.*

The waterfall shows that as soon as `v2.html` has finished loading, the requests
for the assets it references (also referred to as _subresources_) start. The
browser can request multiple subresources at the same time, and that's
represented by the overlapping bars in the Waterfall column for `main.css` and
`logo.svg`. Finally, you can see from the screenshot that `main.js` starts
loading last, and it finishes loading after the other three URLs have completed
as well.


# Imperative caching guide

Some websites might need to communicate to the service worker without the need of being
informed about the result. Here are some examples:

- A page sends the service worker a list of URLs [to
  prefetch](/instant-navigation-experiences/), so that, when the user clicks on a
  link the document or page subresources are already available in the cache, making subsequent
  navigation much faster.
- The page asks the service worker to retrieve and cache a set of top articles, to have them
  available for offline purposes.

Delegating these types of non-critical tasks to the service worker has the benefit of freeing up the
main thread for better handling more pressing tasks such as responding to user interactions.

<figure>
  

*Diagram of a page requesting resources to cache to a service worker.*

</figure>

In this guide we'll explore how to implement a **one-way** communication technique from the page to
the service worker by using standard browser APIs and the Workbox library. We'll call these types of
use cases **imperative caching**.

  Check out [Workers overview](/workers-overview/) for a high-level explanation
  of when to use web workers versus service workers and the rest of the
  [Communicate with workers](/reliable/#communicate-with-workers) series
  for guides on other common use cases.

## Production case {: #production-case }

1-800-Flowers.com implemented **imperative caching** (prefetching) with service workers via
[`postMessage()`](https://developer.mozilla.org/docs/Web/API/Worker/postMessage) to prefetch the
top items in category pages to speed up subsequent navigation to product detail pages.

<figure>
  

*Logo of 1-800 Flowers.*

</figure>

They use a mixed approach to decide which items to prefetch:

- At page load time they ask the servicer worker to retrieve the JSON data for the top 9 items, and
  add the resulting response objects to the cache.
- For the remaining items, they listen to the [`mouseover`
  ](https://developer.mozilla.org/docs/Web/API/Element/mouseover_event) event, so that, when a
  user moves the cursor on top of an item, they can trigger a fetch for the resource on "demand".

They use the [Cache API](https://developer.mozilla.org/docs/Web/API/Cache) to store JSON
responses:

<figure>
  

*Logo of 1-800 Flowers.*

   <figcaption>Prefetching JSON product data from product listing pages in 1-800Flowers.com.</figcaption>
</figure>

When the user clicks on an item, the JSON data associated with it can be picked up from the cache,
without the need of going to the network, making the navigation faster.

## Using Workbox {: #using-workbox }

[Workbox](https://developer.chrome.com/docs/workbox/) provides an easy way to send messages to
a service worker, via the [`workbox-window`](https://developer.chrome.com/docs/workbox/modules/workbox-window/) package, a set of modules
that are intended to run in the window context. They're a complement to the other Workbox packages
that run in the service worker.

To communicate the page with the service worker, first obtain a Workbox object reference to the
registered service worker:

```javascript
const wb = new Workbox('/sw.js');
wb.register();
```

Then you can directly send the message declaratively, without the hassle of the getting the
registration, checking for activation, or thinking about the underlying communication API:

```javascript
wb.messageSW({"type": "PREFETCH", "payload": {"urls": ["/data1.json", "data2.json"]}}); });
```

The service worker implements a [`message`](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope/message_event) handler to
listen to these messages. It can optionally return a response, although, in cases like these, it's
not necessary:

```javascript
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PREFETCH') {
    // do something
  }
});
```

## Using browser APIs {: #using-browser-apis }

If the Workbox library is not enough for your needs, here is how you can implement window to service
worker communication, using browser APIs.

The [postMessage API](https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage)
can be used to establish a **one-way** communication mechanism from the page to the service worker.

The page calls
[`postMessage()`](https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage) on the
service worker interface:

```javascript
navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
  payload: 'some data to perform the task',
});
```
The service worker implements a [`message`](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope/message_event) handler to
listen to these messages.

```javascript
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === MSG_ID) {
    // do something
  }
});
```

The `{type : 'MSG_ID'}` attribute is not absolutely required, but it is one way to allow the page to
send different types of instructions to the service worker (that is, 'to prefetch' vs. 'to clear
storage'). The service worker can branch into different execution paths based on this flag.

If the operation was successful, the user will be able to get the benefits from it but, if not, it won't alter the main user flow. For example, when 1-800-Flowers.com attempts to precache, the page doesn't need to know whether the service worker succeeded. If it does, then the user will enjoy a faster navigation. If it doesn't the page still needs to navigate to the new page. It's just going to take a little longer.

### A simple prefetching example {: #a-simple-prefetching-example }

One of the most common applications of **imperative caching** is **prefetching**, meaning fetching
resources for a given URL, before the user moves to it, in order to speed up navigation.

There are different ways of implementing prefetching in sites:

- Using [Link prefetch tags](/link-prefetch/) in pages: resources are kept in the
  browser cache for five minutes, after which the normal `Cache-Control` rules for the resource
  apply.
- Complementing the previous technique with [a runtime caching strategy in the service
  worker](/instant-navigation-experiences/) to extend the lifetime of the prefetch
  resource beyond this limit.

For relatively simple prefetching scenarios, like prefetching documents, or specific assets (JS,
CSS, etc.), those techniques are the best approach.

If additional logic is required, for example, parsing the prefetch resource (a JSON file or page) in
order to fetch its internal URLs, it's more appropriate to delegate this task entirely to the
service worker.

Delegating these types of operations to the service worker has the following advantages:

- Offloading the heavy lifting of fetching and post-fetch processing (which will be introduced
  later) to a secondary thread. By doing this, it frees the main thread to handle more important
  tasks such as responding to user interactions.
- Allowing multiple clients (e.g. tabs) to reuse a common functionality, and even calling the
  service simultaneously without blocking the main thread.

### Prefetch product detail pages {: #prefetch-product-detail-pages }

First use [`postMessage()`](https://developer.mozilla.org/docs/Web/API/Worker/postMessage) on
the service worker interface and pass an array of URLs to cache:

```javascript
navigator.serviceWorker.controller.postMessage({
  type: 'PREFETCH',
  payload: {
    urls: [
      'www.exmaple.com/apis/data_1.json',
      'www.exmaple.com/apis/data_2.json',
    ],
  },
});
```

In the service worker, implement a [`message`](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope/message_event) handler to
intercept and process messages sent by any active tab:

```javascript
addEventListener('message', (event) => {
  let data = event.data;
  if (data && data.type === 'PREFETCH') {
    let urls = data.payload.urls;
    for (let i in urls) {
      fetchAsync(urls[i]);
    }
  }
});
```
In the previous code we introduced a small helper function called `fetchAsync()` to iterate on the
array of URLs and issue a fetch request for each of them:

```javascript
async function fetchAsync(url) {
  // await response of fetch call
  let prefetched = await fetch(url);
  // (optionally) cache resources in the service worker storage
}
```

When the response is obtained you can rely on the caching headers of the resource. In many cases
though, like in product detail pages, resources are not cached (which means, they have a
`Cache-control` header of `no-cache`). In cases like these you can override this behavior, by
storing the fetched resource in the service worker cache. This has the added benefit of allowing the
file to be served in offline scenarios.

### Beyond JSON data {: #beyond-json-data }

Once the JSON data is fetched from a server endpoint, it often contains other URLs that are also
worth prefetching, such as an image or other endpoint data that are associated with this first-level
data.

Let's say that in our example, the JSON data returned is the information of a grocery shopping site:

```json
{
  "productName": "banana",
  "productPic": "https://cdn.example.com/product_images/banana.jpeg",
  "unitPrice": "1.99"
 }
```

Modify the `fetchAsync()` code to iterate over the list of products and cache the hero image for
each of them:

```javascript
async function fetchAsync(url, postProcess) {
  // await response of fetch call
  let prefetched = await fetch(url);

  //(optionally) cache resource in the service worker cache

  // carry out the post fetch process if supplied
  if (postProcess) {
    await postProcess(prefetched);
  }
}

async function postProcess(prefetched) {
  let productJson = await prefetched.json();
  if (productJson && productJson.product_pic) {
    fetchAsync(productJson.product_pic);
  }
}
```

You can add some exception handling around this code for situations like 404s.
But the beauty of using a service worker to prefetch is that it can fail without much
consequence to the page and the main thread. You may also have more elaborate logic in the
post-processing of the prefetched content, making it more flexible and decoupled with the data it's
handling. The sky's the limit.

 Prefetching techniques consume extra bytes for resources that are not
immediately needed, so it needs to be applied thoughtfully; only prefetch resources when you are
confident that users will need them. Avoid prefetching when users are on slow connections.
You can detect that with the [Network Information
API](https://developer.mozilla.org/docs/Web/API/Network_Information_API). 

## Conclusion {: #conclusion }

In this article, we covered a common use case of **one-way** communication between page and service
worker: **imperative caching**. The examples discussed are only meant for demonstrating one way of
using this pattern and the same approach can be applied to other use cases as well, for example,
caching top articles on demand for offline consumption, bookmarking, and others.

For more patterns of page and service worker communication, check out:

- [Broadcast updates](/broadcast-updates-guide): Calling the page from the service worker to inform
  about important updates (e.g. a new version of the webapp is available).
- [Two-way communication](/two-way-communication-guide): Delegating a task to a service worker (e.g.
  a heavy download), and keeping the page informed on the progress.


# Instant navigation experiences



https://www.youtube.com/watch?v=fhqCwDP69PI

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.

In technical terms, moving through different pages means making a **navigation request**. As a general rule, you **don't** want to use long-lived `Cache-Control` headers to cache the HTML response for a navigation request. They should normally be satisfied via the network, with `Cache-Control: no-cache`, 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't be *reliably* fast.

To speed up these requests, if you can anticipate the user'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 [prefetching](/link-prefetch/) and it's commonly implemented by adding `<link rel="prefetch">` tags to pages, indicating the resource to prefetch.

In this guide we'll explore different ways in which [service workers](https://developer.mozilla.org/docs/Web/API/Service_Worker_API) can be used as a complement of traditional prefetching techniques.

## Production cases

[MercadoLibre](https://www.mercadolibre.com.ar/) is the biggest e-commerce site in Latin America. To speed up navigations, they dynamically inject `<link rel="prefetch">` 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:

<figure>
  

*Screenshot of MercadoLibre's listing pages one and two and a Link Prefetch tag connecting both.*

</figure>

Prefetched files are requested at the "Lowest" priority and stored in the [HTTP cache](/http-cache/) or the [memory cache](https://calendar.perfplanet.com/2016/a-tale-of-four-caches/) (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 `Cache-Control` rules for the resource apply.

Using service worker caching can help you extend the lifetime of prefetch resources beyond the five-minute window.

For example, Italian sports portal [Virgilio Sport](https://sport.virgilio.it/) uses service workers to prefetch the most popular posts in their home page. They also use the [Network Information API](https://developer.mozilla.org/docs/Web/API/Network_Information_API) to avoid prefetching for users that are on a 2G connection.

<figure>
  

*Virgilio Sport logo.*

</figure>

As a result of this, over 3 weeks of observation Virgilio Sport witnessed load times for navigation to articles improve **78%**, and the number of article impressions increase **45%**.

<figure>
  

*A screenshot of Virgilio Sport home and article pages, with impact metrics after prefetching.*

</figure>

## Implement precaching with Workbox

In the following section we'll use [Workbox](/workbox/) to show how to implement different caching techniques in the service worker that can be used as a complement to `<link rel="prefetch">`, or even a replacement for it, by delegating this task completely to the service worker.

 You must take steps to ensure that adding a service worker to your site doesn'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 [Building Faster, More Resilient Apps with Service Workers](https://www.youtube.com/watch?v=25aCD5XL1Jk)). You can mitigate this overhead by enabling a feature called [navigation preload](https://developer.chrome.com/blog/navigation-preload/), and then using the [network response](https://developer.chrome.com/blog/navigation-preload/#using-the-preloaded-response) that's been preloaded inside of your fetch event handler.

### 1. Precache static pages and page subresources

[Precaching](/precache-with-workbox/) is the ability of the service worker to save files to the cache while it's installing.

Precaching sounds similar to prefetching, but it's a different technique. In the first one, the service worker fetches and stores resources (typically static files) while it'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.

In the following cases precaching is used to achieve a similar goal as prefetching: making navigations faster.

#### Precaching static pages

For pages that are generated at build time (e.g. `about.html`, `contact.html`), or in completely static sites, one can just add the site's documents to the precache list, so they are already available in the cache every time the user accesses them:

```javascript
workbox.precaching.precacheAndRoute([
  {url: '/about.html', revision: 'abcd1234'},
  // ... other entries ...
]);
```

#### Precaching page subresources

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.

To speed up navigations in an e-commerce site, you can use  `<link rel="prefetch">` 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.

To implement this:

- Add a `<link rel="prefetch">` tag to the page:

```html
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
```

- Add the page subresources to the precache list in the service worker:

```javascript
workbox.precaching.precacheAndRoute([
  '/styles/product-page.ac29.css',
  // ... other entries ...
]);
```

### 2. Extend the lifetime of prefetch resources

As mentioned earlier, `<link rel="prefetch">` fetches and keeps resources in the HTTP cache for a limited amount of time, after which point the `Cache-Control` rules for a resource apply. As of Chrome 85, this value is 5 minutes.

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.

In the previous example, one could complement the `<link rel="prefetch">` used to prefetch a product page with a [Workbox runtime caching strategy](/runtime-caching-with-workbox/).

To implement that:

- Add a `<link rel="prefetch">` tag to the page:

```html
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
 ```

 - Implement a runtime caching strategy in the service worker for these types of requests:

```javascript
new workbox.strategies.StaleWhileRevalidate({
  cacheName: 'document-cache',
  plugins: [
    new workbox.expiration.Plugin({
      maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
    }),
  ],
});
```

In this case, we have opted to use a [stale-while-revalidate strategy](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#stale-while-revalidate). 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.

### 3. Delegate prefetching to the service worker

In most cases the best approach is to use `<link rel="prefetch">`. The tag is a [resource hint](https://www.w3.org/TR/resource-hints/) designed to make prefetching as efficient as possible.

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 `<link rel="prefetch">` tags dynamically in the page, based on an API response. This can momentarily consume time on the page's main thread and make the implementation more difficult.

In cases like this, use a "page to service worker communication strategy", to delegate the task of prefetching completely to the service worker. This type of communication can be achieved by using [worker.postMessage()](https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage):

<figure>
  

*An icon of a page making two way communication with a service worker.*

</figure>

The [Workbox Window package](https://developer.chrome.com/docs/workbox/modules/workbox-window/) simplifies this type of communication, abstracting many details of the underlying call being done.

Prefetching with Workbox Window can be implemented in the following way:

- In the page: call the service worker passing it the type of message, and the list of URLs to prefetch:

```javascript
const wb = new Workbox('/sw.js');
wb.register();

const prefetchResponse = await wb.messageSW({type: 'PREFETCH_URLS', urls: […]});
```

- In the service worker: implement a message handler to issue a `fetch()` request for each URL to prefetch:

```javascript
addEventListener('message', (event) => {
  if (event.data.type === 'PREFETCH_URLS') {
    // Fetch URLs and store them in the cache
  }
});
```


# Measuring offline usage

This article shows you how to track offline usage of your site to help you make a case for why your
site needs a better offline mode. It also explains pitfalls and problems to avoid when implementing
offline usage analytics.

## The pitfalls of the online and offline browser events

The obvious solution for tracking offline usage is to create event listeners for the
[`online`](https://developer.mozilla.org/docs/Web/API/Window/online_event) and
[`offline`](https://developer.mozilla.org/docs/Web/API/Window/offline_event) events (which
[many browsers support](https://caniuse.com/#feat=online-status)) and to put your analytics tracking
logic in those listeners. Unfortunately, there are several problems and limitations with this
approach:

+   In general tracking every network connection status event might be excessive, and is
    counter-productive in a privacy-centric world where as little data as possible should be
    collected. Additionally the `online` and `offline` events can fire for just a split second of
    network loss, which a user probably wouldn't even see or notice.
+   The analytics tracking of offline activity would never reach the analytics server because
    the user is… well, offline.
+   Tracking a timestamp locally when a user goes offline and sending the offline activity to
    the analytics server when the user goes back online depends on the user revisiting your site.
    If the user drops off your site due to a lack of an offline mode and never revisits, you have
    no way to track that. The ability to track offline drop-offs is critical data for building a
    case about why your site needs a better offline mode.
+   The `online` event is not very reliable as it
    [only knows about network access](https://developer.mozilla.org/docs/Web/API/NavigatorOnLine/onLine),
    not internet access. Therefore a user might still be offline, and sending the tracking ping can
    still fail.
+   Even if the user still stays on the current page while being offline, none of the other
    analytics events (e.g. scroll events, clicks, etc.) are tracked either, which might be the more
    relevant and useful information.
+   Being offline in itself is also not too meaningful in general. As a website developer it may
    be more important to know what kinds of resources failed to load. This is especially relevant
    in the context of SPAs, where a dropped network connection might not lead to a browser offline
    error page (which users understand) but more likely to random dynamic parts of the page failing
    silently.

You can still use this solution to gain a basic understanding of offline usage, but the many
drawbacks and limitations need to be considered carefully.

## A better approach: the service worker

The solution that enables offline mode turns out to be the better solution for tracking offline
usage.  The basic idea is to store analytics pings into IndexedDB as long as the user is offline,
and just resend them when the user goes online again. For Google Analytics this is already available
[off-the-shelf through a Workbox module](https://developer.chrome.com/docs/workbox/modules/workbox-google-analytics/),
but keep in mind that hits sent more than
[four hours deferred](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters#qt)
may not be processed. In its simplest form, it can be activated within a Workbox-based service
worker with these two lines:

```js
import * as googleAnalytics from 'workbox-google-analytics';

googleAnalytics.initialize();
```

This tracks all existing events and pageview pings while being offline, but you wouldn't know that
they happened offline (as they are just replayed as-is). For this
[you can manipulate tracking requests with Workbox](https://developer.chrome.com/docs/workbox/modules/workbox-google-analytics/#using-a-custom-dimension-to-track-online-vs-offline-interactions)
by adding an `offline` flag to the analytics ping, using a custom dimension (`cd1` in the code
sample below):

```js
import * as googleAnalytics from 'workbox-google-analytics';

googleAnalytics.initialize({
  parameterOverrides: {
    cd1: 'offline',
  },
});
```

What if the user drops out of the page due to being offline, before an internet connection comes
back? Even though this normally puts the service worker to sleep (because it's unable to send the data
when the connection comes back), the Workbox Google Analytics module uses the [Background Sync
API](https://developer.chrome.com/blog/background-sync/), which sends the analytics
data later when the connection comes back, even if the user closes the tab or browser.

There is still a drawback: while this makes existing tracking offline-capable, you would most likely
not see much relevant data coming in until you implement a basic offline mode. Users would still
drop off your site quickly when the connection breaks away. But now you can at least measure and
quantify this, by comparing average session length and user engagement for users with the offline
dimension applied versus your regular users.

## SPAs and lazy loading

If users visiting a page built as a multi-page website go offline and try to navigate, the browser's
default offline page shows up, helping users understand what is happening. However, pages built as
single-page applications work differently. The user stays on the same page, and new content is
loaded dynamically through AJAX without any browser navigation. Users do not see the browser error
page when going offline. Instead, the dynamic parts of the page render with errors, go into
undefined states, or just stop being dynamic.

Similar effects can happen within multi-page websites due to lazy loading. For example, maybe the
initial load happened online, but the user went offline before scrolling. All lazy loaded content
below the fold will silently fail and be missing.

As these cases are really irritating to users, it makes sense to track them. Service workers are the
perfect spot to catch network errors, and eventually track them using analytics. With Workbox, a
[global catch handler](https://developer.chrome.com/docs/workbox/managing-fallback-responses/#comprehensive-fallbacks)
can be configured to inform the page about failed requests by sending a message event:

```js
import { setCatchHandler } from 'workbox-routing';

setCatchHandler(({ event }) => {
  // https://developer.mozilla.org/docs/Web/API/Client/postMessage
  event.waitUntil(async function () {
    // Exit early if we don't have access to the client.
    // Eg, if it's cross-origin.
    if (!event.clientId) return;

    // Get the client.
    const client = await clients.get(event.clientId);
    // Exit early if we don't get the client.
    // Eg, if it closed.
    if (!client) return;

    // Send a message to the client.
    client.postMessage({
      action: "network_fail",
      url: event.request.url,
      destination: event.request.destination
    });

    return Response.error();

  }());
});
```

Rather than listening to all failed requests, another way is to catch errors on specific routes
only. As an example, if we want to report errors happening on routes to `/products/*` only, we can
add a check in `setCatchHandler` which filters the URI with a regular expression.
A cleaner solution is to implement registerRoute with a custom handler. This encapsulates the
business logic into a separate route, with better maintainability in more complex service workers:

```js
import { registerRoute } from 'workbox-routing';
import { NetworkOnly } from 'workbox-strategies';

const networkOnly = new NetworkOnly();
registerRoute(
  new RegExp('https:\/\/example\.com\/products\/.+'),
  async (params) => {
    try {
      // Attempt a network request.
      return await networkOnly.handle(params);
    } catch (error) {
      // If it fails, report the error.
      const event = params.event;
      if (!event.clientId) return;
      const client = await clients.get(event.clientId);
      if (!client) return;

      client.postMessage({
        action: "network_fail",
        url: event.request.url,
        destination: "products"
      });

      return Response.error();
    }
  }
);
```

As a final step, the page needs to listen to the `message` event, and send out the analytics ping.
Again, make sure to buffer analytics requests that happen offline within the service worker. As
described before, initialize the `workbox-google-analytics` plugin for built-in Google Analytics
support.

The following example uses Google Analytics, but can be applied in the same way for other analytics
vendors.

```js
if ("serviceWorker" in navigator) {
  // ... SW registration here

  // track offline error events
  navigator.serviceWorker.addEventListener("message", event => {
    if (gtag && event.data && event.data.action === "network_fail") {
      gtag("event", "network_fail", {
        event_category: event.data.destination,
        // event_label: event.data.url,
        // value: event.data.value
      });
    }
  });
}
```

This will track failed resource loads in Google Analytics, where they can be analyzed with
[reporting](https://support.google.com/analytics/answer/1033068?hl=en). The derived insight can be
used to improve service worker caching and error handling in general, to make the page more robust
and reliable under unstable network conditions.

## Next steps

This article showed different ways of tracking offline usage with their advantages and shortcomings.
While this can help to quantify how many of your users go offline and run into problems due to it,
it's still just a start. As long as your website does not offer a well-built offline mode, you
obviously won't see much offline usage in analytics.

We recommend to get the full tracking in place, and then extend your offline capabilities in
iterations with an eye on tracking numbers. Start with a simple offline error page first–with
[Workbox it's trivial to
do](https://developer.chrome.com/docs/workbox/managing-fallback-responses/#offline-page-only)–and
should be considered a UX best practice similar to custom 404 pages anyway. Then work your way
[towards more advanced offline fallbacks](https://developer.chrome.com/docs/workbox/managing-fallback-responses/#comprehensive-fallbacks)
and finally towards real offline content. Make sure you advertise and explain this to your users
well, and you will see increasing usage. After all, everyone goes offline every once in a while.

Check out [How to report metrics and build a performance culture](/how-to-report-metrics/)
and [Fixing website speed cross-functionally](/fixing-website-speed-cross-functionally/) for tips
on persuading cross-functional stakeholders to invest more in your website. Although those posts
are focused on performance, they should help you get general ideas about how to engage
stakeholders.

Hero photo by [JC Gellidon][hero] on [Unsplash][unsplash].

[hero]: https://unsplash.com/@jcgellidon?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText
[unsplash]: https://unsplash.com/s/photos/subway-people?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText

# What is network reliability and how do you measure it?

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 _reliable_ experience on the web for all of
your users can be challenging. It can be a challenge just to understand what
reliability means.

## Reliable while offline

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're currently connected to the
Internet.

Until recently, it's been a challenge to build web applications that are
reliable without a network connection.

## Reliably fast

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're on a cellular connection as they do
when they're on wi-fi? And what about users who have a high-latency, or
"[lie-fi](/performance-poor-connectivity/#what-is-lie-fi)"
connection. Will your web app be reliably fast even in those scenarios?

It's not enough to  be fast under the best circumstances. Your users will view
your web app's performance through the lens of how it behaves in all network
conditions.

## Reliable is achievable

The good news is that the modern web platform provides technologies—such as
[service workers](https://developer.mozilla.org/docs/Web/API/Service_Worker_API) and the
[Cache Storage API](https://developer.mozilla.org/docs/Web/API/CacheStorage)—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's requests.

## Your guiding light: Responds with a 200 OK while offline

Once you start building out a service worker and serving content from caches,
it's hard to know if you'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?

[Lighthouse](https://developer.chrome.com/docs/lighthouse/overview/) provides one
specific test that is of particular interest when building a reliable web app:
**Responds with a 200 OK while offline**:

<figure>
  

*Lighthouse's progressive web app report showing a passing responds with a 200 when offline audit.*

</figure>

The actual interface may differ depending on which version of Lighthouse
you're running.

What'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 *reliable while offline*—using a controlled, repeatable sequence of
actions.

## It's a journey

If you're just starting out, then there's a very good chance that you'll get
back a negative result for the Responds with a 200 while offline check. That's
okay!  Unless you're using a customized starter project, web applications don'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.

Throughout this process, you'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.


# The Offline Cookbook

With [Service Worker][sw_primer] we gave up trying to solve offline, and gave developers the moving
parts to go solve it themselves. It gives you control over caching and how requests are handled.
That means you get to create your own patterns. Let's take a look at a few possible patterns in
isolation, but in practice you'll likely use many of them in tandem depending on URL and context.

For a working demo of some of these patterns, see [Trained-to-thrill][ttt], and
[this video](https://www.youtube.com/watch?v=px-J9Ghvcx4) showing the performance impact.

## The cache machine—when to store resources

[Service Worker][sw_primer] lets you handle requests independently from caching, so I'll demonstrate
them separately. First up, caching, when should it be done?

### On install—as a dependency {: #on-install-as-dependency }

<figure>
  

*On install - as a dependency.*

  <figcaption>On install - as a dependency.</figcaption>
</figure>

Service Worker gives you an `install` event. You can use this to get stuff ready, stuff that must be
ready before you handle other events. While this happens any previous version of your Service Worker
is still running and serving pages, so the things you do here mustn't disrupt that.

**Ideal for:** CSS, images, fonts, JS, templates… basically anything you'd consider static to that
"version" of your site.

These are things that would make your site entirely non-functional if they failed to be fetched, things
an equivalent platform-specific app would make part of the initial download.

```js
self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('mysite-static-v3').then(function (cache) {
      return cache.addAll([
        '/css/whatever-v3.css',
        '/css/imgs/sprites-v6.png',
        '/css/fonts/whatever-v8.woff',
        '/js/all-min-v4.js',
        // etc.
      ]);
    }),
  );
});
```

`event.waitUntil` takes a promise to define the length and success of the install. If the promise
rejects, the installation is considered a failure and this Service Worker will be abandoned (if an
older version is running, it'll be left intact). `caches.open()` and `cache.addAll()` return promises.
If any of the resources fail to be fetched, the `cache.addAll()` call rejects.

On [trained-to-thrill][ttt] I use this to
[cache static assets](https://github.com/jakearchibald/trained-to-thrill/blob/3291dd40923346e3cc9c83ae527004d502e0464f/www/static/js-unmin/sw/index.js#L3).

### On install—not as a dependency {: #on-install-not }

<figure>
  

*On install - not as a dependency.*

  <figcaption>On install - not as a dependency.</figcaption>
</figure>

This is similar to above, but won't delay install completing and won't cause installation to fail if caching
fails.

**Ideal for:** bigger resources that aren't needed straight away, such as assets for later levels of
a game.

```js
self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('mygame-core-v1').then(function (cache) {
      cache
        .addAll
        // levels 11–20
        ();
      return cache
        .addAll
        // core assets and levels 1–10
        ();
    }),
  );
});
```

The above example does not pass the `cache.addAll` promise for levels 11–20 back to
`event.waitUntil`, so even if it fails, the game will still be available offline. Of course, you'll
have to cater for the possible absence of those levels and reattempt caching them if they're
missing.

The Service Worker may be killed while levels 11–20 download since it's finished handling events,
meaning they won't be cached. In future the
[Web Periodic Background Synchronization API](https://developer.mozilla.org/docs/Web/API/Web_Periodic_Background_Synchronization_API)
will handle cases like this, and larger downloads such as movies. That API is currently only
supported on Chromium forks.

### On activate {: #on-activate }

<figure>
  

*On activate.*

  <figcaption>On activate.</figcaption>
</figure>

**Ideal for:** clean-up and migration.

Once a new Service Worker has installed and a previous version isn't being used, the new one
activates, and you get an `activate` event. Because the old version is out of the way, it's a good
time to handle
[schema migrations in IndexedDB](/indexeddb-best-practices/)
and also delete unused caches.

```js
self.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys().then(function (cacheNames) {
      return Promise.all(
        cacheNames
          .filter(function (cacheName) {
            // Return true if you want to remove this cache,
            // but remember that caches are shared across
            // the whole origin
          })
          .map(function (cacheName) {
            return caches.delete(cacheName);
          }),
      );
    }),
  );
});
```

During activation, other events such as `fetch` are put into a queue, so a long activation could
potentially block page loads. Keep your activation as lean as possible, and only use it for things you
_couldn't_ do while the old version was active.

On [trained-to-thrill][ttt] I use this to
[remove old caches](https://github.com/jakearchibald/trained-to-thrill/blob/3291dd40923346e3cc9c83ae527004d502e0464f/www/static/js-unmin/sw/index.js#L17).

### On user interaction {: #on-user-interaction }

<figure>
  

*On user interaction.*

  <figcaption>On user interaction.</figcaption>
</figure>

**Ideal for:** when the whole site can't be taken offline, and you chose to allow the user to select
the content they want available offline. E.g. a video on something like YouTube, an article on
Wikipedia, a particular gallery on Flickr.

Give the user a "Read later" or "Save for offline" button. When it's clicked, fetch what you need
from the network and pop it in the cache.

```js
document.querySelector('.cache-article').addEventListener('click', function (event) {
  event.preventDefault();

  var id = this.dataset.articleId;
  caches.open('mysite-article-' + id).then(function (cache) {
    fetch('/get-article-urls?id=' + id)
      .then(function (response) {
        // /get-article-urls returns a JSON-encoded array of
        // resource URLs that a given article depends on
        return response.json();
      })
      .then(function (urls) {
        cache.addAll(urls);
      });
  });
});
```

The [caches API][caches_api] is available from pages as well as service workers, meaning you
can add to the cache directly from the page.

### On network response {: #on-network-response }

<figure>
  

*On network response.*

  <figcaption>On network response.</figcaption>
</figure>

**Ideal for:** frequently updating resources such as a user's inbox, or article contents. Also
useful for non-essential content such as avatars, but care is needed.

If a request doesn't match anything in the cache, get it from the network, send it to the page, and
add it to the cache at the same time.

If you do this for a range of URLs, such as avatars, you'll need to be careful you don't bloat the
storage of your origin. If the user needs to reclaim disk space you don't want to be the prime
candidate. Make sure you get rid of items in the cache you don't need any more.

```js
self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return cache.match(event.request).then(function (response) {
        return (
          response ||
          fetch(event.request).then(function (response) {
            cache.put(event.request, response.clone());
            return response;
          })
        );
      });
    }),
  );
});
```

To allow for efficient memory usage, you can only read a response/request's body once. The code
above uses [`.clone()`](https://fetch.spec.whatwg.org/#dom-request-clone) to create additional
copies that can be read separately.

On [trained-to-thrill][ttt] I use this to
[cache Flickr images](https://github.com/jakearchibald/trained-to-thrill/blob/3291dd40923346e3cc9c83ae527004d502e0464f/www/static/js-unmin/sw/index.js#L109).

### Stale-while-revalidate {: #stale-while-revalidate }

<figure>
  

*Stale-while-revalidate.*

  <figcaption>Stale-while-revalidate.</figcaption>
</figure>

**Ideal for:** frequently updating resources where having the very latest version is non-essential.
Avatars can fall into this category.

If there's a cached version available, use it, but fetch an update for next time.

```js
self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return cache.match(event.request).then(function (response) {
        var fetchPromise = fetch(event.request).then(function (networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        });
        return response || fetchPromise;
      });
    }),
  );
});
```

This is very similar to HTTP's [stale-while-revalidate](https://www.mnot.net/blog/2007/12/12/stale).

### On push message {: #on-push-message }

<figure>
  

*On push message.*

  <figcaption>On push message.</figcaption>
</figure>

The [Push API](/push-notifications/) is another feature built on top of
Service Worker. This allows the Service Worker to be awoken in response to a message from the OS's
messaging service. This happens even when the user doesn't have a tab open to your site. Only the
Service Worker is woken up. You request permission to do this from a page and the user will be
prompted.

**Ideal for:** content relating to a notification, such as a chat message, a breaking news story, or
an email. Also infrequently changing content that benefits from immediate sync, such as a todo list
update or a calendar alteration.

https://www.youtube.com/watch?v=0i7YdSEQI1w

The common final outcome is a notification which, when tapped, opens/focuses a relevant page, but
for which updating caches before this happens is _extremely_ important. The user is obviously online at the
time of receiving the push message, but they may not be when they finally interact with the
notification, so making this content available offline is important.

This code updates caches before showing a notification:

```js
self.addEventListener('push', function (event) {
  if (event.data.text() == 'new-email') {
    event.waitUntil(
      caches
        .open('mysite-dynamic')
        .then(function (cache) {
          return fetch('/inbox.json').then(function (response) {
            cache.put('/inbox.json', response.clone());
            return response.json();
          });
        })
        .then(function (emails) {
          registration.showNotification('New email', {
            body: 'From ' + emails[0].from.name,
            tag: 'new-email',
          });
        }),
    );
  }
});

self.addEventListener('notificationclick', function (event) {
  if (event.notification.tag == 'new-email') {
    // Assume that all of the resources needed to render
    // /inbox/ have previously been cached, e.g. as part
    // of the install handler.
    new WindowClient('/inbox/');
  }
});
```

### On background-sync {: #on-background-sync }

<figure>
  

*On background-sync.*

  <figcaption>On background-sync.</figcaption>
</figure>

[Background sync](https://developer.chrome.com/blog/background-sync/) is another feature built on top of
Service Worker. It allows you to request background data synchronization as a one-off, or on an
(extremely heuristic) interval. This happens even when the user doesn't have a tab open to your
site. Only the Service Worker is woken up. You request permission to do this from a page and the user
will be prompted.

**Ideal for:** non-urgent updates, especially those that happen so regularly that a push message per
update would be too frequent for users, such as social timelines or news articles.

```js
self.addEventListener('sync', function (event) {
  if (event.id == 'update-leaderboard') {
    event.waitUntil(
      caches.open('mygame-dynamic').then(function (cache) {
        return cache.add('/leaderboard.json');
      }),
    );
  }
});
```

## Cache persistence {: #cache-persistence }

Your origin is given a certain amount of free space to do what it wants with. That free space is
shared between all origin storage:
[(local) Storage](https://developer.mozilla.org/docs/Web/API/Storage),
[IndexedDB](https://developer.mozilla.org/docs/Glossary/IndexedDB),
[File System Access](/file-system-access/), and of course
[Caches](https://developer.mozilla.org/docs/Web/API/Cache).

The amount you get isn't spec'd. It will differ depending on device and storage conditions. You can
find out how much you've got via:

```js
navigator.storageQuota.queryInfo('temporary').then(function (info) {
  console.log(info.quota);
  // Result: <quota in bytes>
  console.log(info.usage);
  // Result: <used data in bytes>
});
```

However, like all browser storage, the browser is free to throw away your data if the device comes
under storage pressure. Unfortunately the browser can't tell the difference between those movies you
want to keep at all costs, and the game you don't really care about.

To work around this, use the
[StorageManager](https://developer.mozilla.org/docs/Web/API/StorageManager) interface:

```js
// From a page:
navigator.storage.persist()
.then(function(persisted) {
  if (persisted) {
    // Hurrah, your data is here to stay!
  } else {
   // So sad, your data may get chucked. Sorry.
});
```

Of course, the user has to grant permission.  For this, use the Permissions API.

Making the user part of this flow is important, as we
can now expect them to be in control of deletion. If their device comes under storage pressure, and
clearing non-essential data doesn't solve it, the user gets to judge which items to keep and
remove.

For this to work, it requires operating systems to treat "durable" origins as equivalent to platform-specific
apps in their breakdowns of storage usage, rather than reporting the browser as a single item.

## Serving Suggestions—responding to requests {: #serving-suggestions }

It doesn't matter how much caching you do, the service worker won't use the cache unless you tell it
when and how. Here are a few patterns for handling requests:

### Cache only {: #cache-only }

<figure>
  

*Cache only.*

  <figcaption>Cache only.</figcaption>
</figure>

**Ideal for:** anything you'd consider static to a particular "version" of your site. You should
have cached these in the install event, so you can depend on them being there.

```js
self.addEventListener('fetch', function (event) {
  // If a match isn't found in the cache, the response
  // will look like a connection error
  event.respondWith(caches.match(event.request));
});
```

…although you don't often need to handle this case specifically,
[Cache, falling back to network](#cache-falling-back-to-network) covers it.

### Network only {: #network-only }

<figure>
  

*Network only.*

  <figcaption>Network only.</figcaption>
</figure>

**Ideal for:** things that have no offline equivalent, such as analytics pings, non-GET requests.

```js
self.addEventListener('fetch', function (event) {
  event.respondWith(fetch(event.request));
  // or simply don't call event.respondWith, which
  // will result in default browser behavior
});
```

…although you don't often need to handle this case specifically,
[Cache, falling back to network](#cache-falling-back-to-network) covers it.

### Cache, falling back to network {: #cache-falling-back-to-network }

<figure>
  

*Cache, falling back to network.*

  <figcaption>Cache, falling back to network.</figcaption>
</figure>

**Ideal for:** building offline-first. In such cases, this is how you'll handle the majority of
requests. Other patterns will be exceptions based on the incoming request.

```js
self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      return response || fetch(event.request);
    }),
  );
});
```

This gives you the "cache only" behavior for things in the cache and the "network only" behavior
for anything not-cached (which includes all non-GET requests, as they cannot be cached).

### Cache and network race {: #cache-and-network-race }

<figure>
  

*Cache and network race.*

  <figcaption>Cache and network race.</figcaption>
</figure>

**Ideal for:** small assets where you're chasing performance on devices with slow disk access.

With some combinations of older hard drives, virus scanners, and faster internet connections,
getting resources from the network can be quicker than going to disk. However, going to the network
when the user has the content on their device can be a waste of data, so bear that in mind.

```js
// Promise.race is no good to us because it rejects if
// a promise rejects before fulfilling. Let's make a proper
// race function:
function promiseAny(promises) {
  return new Promise((resolve, reject) => {
    // make sure promises are all promises
    promises = promises.map((p) => Promise.resolve(p));
    // resolve this promise as soon as one resolves
    promises.forEach((p) => p.then(resolve));
    // reject if all promises reject
    promises.reduce((a, b) => a.catch(() => b)).catch(() => reject(Error('All failed')));
  });
}

self.addEventListener('fetch', function (event) {
  event.respondWith(promiseAny([caches.match(event.request), fetch(event.request)]));
});
```

### Network falling back to cache {: #network-falling-back-to-cache }

<figure>
  

*Network falling back to cache.*

  <figcaption>Network falling back to cache.</figcaption>
</figure>

**Ideal for:** a quick-fix for resources that update frequently, outside of the "version" of the
site. E.g. articles, avatars, social media timelines, and game leader boards.

This means you give online users the most up-to-date content, but offline users get an older cached
version. If the network request succeeds you'll most likely want to
[update the cache entry](#on-network-response).

However, this method has flaws. If the user has an intermittent or slow connection they'll have to
wait for the network to fail before they get the perfectly acceptable content already on their
device. This can take an extremely long time and is a frustrating user experience. See the next
pattern, [Cache then network](#cache-then-network), for a better solution.

```js
self.addEventListener('fetch', function (event) {
  event.respondWith(
    fetch(event.request).catch(function () {
      return caches.match(event.request);
    }),
  );
});
```

### Cache then network {: #cache-then-network }

<figure>
  

*Cache then network.*

  <figcaption>Cache then network.</figcaption>
</figure>

**Ideal for:** content that updates frequently. E.g. articles, social media timelines, and games.
leaderboards.

This requires the page to make two requests, one to the cache, and one to the network. The idea is to
show the cached data first, then update the page when/if the network data arrives.

Sometimes you can just replace the current data when new data arrives (e.g. game leaderboard), but
that can be disruptive with larger pieces of content. Basically, don't "disappear" something the
user may be reading or interacting with.

Twitter adds the new content above the old content and adjusts the scroll position so the user is
uninterrupted. This is possible because Twitter mostly retains a mostly-linear order to content. I
copied this pattern for [trained-to-thrill][ttt] to get content on screen as fast as possible, while
displaying up-to-date content as soon as it arrives.

**Code in the page:**

```js
var networkDataReceived = false;

startSpinner();

// fetch fresh data
var networkUpdate = fetch('/data.json')
  .then(function (response) {
    return response.json();
  })
  .then(function (data) {
    networkDataReceived = true;
    updatePage(data);
  });

// fetch cached data
caches
  .match('/data.json')
  .then(function (response) {
    if (!response) throw Error('No data');
    return response.json();
  })
  .then(function (data) {
    // don't overwrite newer network data
    if (!networkDataReceived) {
      updatePage(data);
    }
  })
  .catch(function () {
    // we didn't get cached data, the network is our last hope:
    return networkUpdate;
  })
  .catch(showErrorMessage)
  .then(stopSpinner);
```

**Code in the Service Worker:**

You should always go to the network and update a cache as you go.

```js
self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return fetch(event.request).then(function (response) {
        cache.put(event.request, response.clone());
        return response;
      });
    }),
  );
});
```

In [trained-to-thrill][ttt] I worked around this by using
[XHR instead of fetch](https://github.com/jakearchibald/trained-to-thrill/blob/3291dd40923346e3cc9c83ae527004d502e0464f/www/static/js-unmin/utils.js#L3),
and abusing the Accept header to tell the Service Worker where to get the result from
([page code](https://github.com/jakearchibald/trained-to-thrill/blob/3291dd40923346e3cc9c83ae527004d502e0464f/www/static/js-unmin/index.js#L70),
[Service Worker code](https://github.com/jakearchibald/trained-to-thrill/blob/3291dd40923346e3cc9c83ae527004d502e0464f/www/static/js-unmin/sw/index.js#L61)).

### Generic fallback {: #generic-fallback }

<figure>
  

*Generic fallback.*

  <figcaption>Generic fallback.</figcaption>
</figure>

If you fail to serve something from the cache and/or network you may want to provide a generic
fallback.

**Ideal for:** secondary imagery such as avatars, failed POST requests, and an "Unavailable while offline".
page.

```js
self.addEventListener('fetch', function (event) {
  event.respondWith(
    // Try the cache
    caches
      .match(event.request)
      .then(function (response) {
        // Fall back to network
        return response || fetch(event.request);
      })
      .catch(function () {
        // If both fail, show a generic fallback:
        return caches.match('/offline.html');
        // However, in reality you'd have many different
        // fallbacks, depending on URL and headers.
        // Eg, a fallback silhouette image for avatars.
      }),
  );
});
```

The item you fallback to is likely to be an [install dependency](#on-install-as-dependency).

If your page is posting an email, your service worker may fall back to storing the email in an IndexedDB
'outbox' and respond by letting the page know that the send failed but the data was successfully
retained.

### Service worker-side templating {: #Service Worker-side-templating }

<figure>
  

*ServiceWorker-side templating.*

  <figcaption>ServiceWorker-side templating.</figcaption>
</figure>

**Ideal for:** pages that cannot have their server response cached.

[Rendering pages on the server makes things fast](https://jakearchibald.com/2013/progressive-enhancement-is-faster/),
but that can mean including state data that may not make sense in a cache, e.g. "Logged in as…". If
your page is controlled by a service worker, you may instead choose to request JSON data along with a
template, and render that instead.

```js
importScripts('templating-engine.js');

self.addEventListener('fetch', function (event) {
  var requestURL = new URL(event.request.url);

  event.respondWith(
    Promise.all([
      caches.match('/article-template.html').then(function (response) {
        return response.text();
      }),
      caches.match(requestURL.path + '.json').then(function (response) {
        return response.json();
      }),
    ]).then(function (responses) {
      var template = responses[0];
      var data = responses[1];

      return new Response(renderTemplate(template, data), {
        headers: {
          'Content-Type': 'text/html',
        },
      });
    }),
  );
});
```

## Putting it together

You aren't limited to one of these methods. In fact, you'll likely use many of them depending on request
URL. For example, [trained-to-thrill][ttt] uses:

- [cache on install](#on-install-as-dependency), for the static UI and behavior
- [cache on network response](#on-network-response), for the Flickr images and data
- [fetch from cache, falling back to network](#cache-falling-back-to-network), for most requests
- [fetch from cache, then network](#cache-then-network), for the Flickr search results

Just look at the request and decide what to do:

```js
self.addEventListener('fetch', function (event) {
  // Parse the URL:
  var requestURL = new URL(event.request.url);

  // Handle requests to a particular host specifically
  if (requestURL.hostname == 'api.example.com') {
    event.respondWith(/* some combination of patterns */);
    return;
  }
  // Routing for local URLs
  if (requestURL.origin == location.origin) {
    // Handle article URLs
    if (/^\/article\//.test(requestURL.pathname)) {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (/\.webp$/.test(requestURL.pathname)) {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (request.method == 'POST') {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (/cheese/.test(requestURL.pathname)) {
      event.respondWith(
        new Response('Flagrant cheese error', {
          status: 512,
        }),
      );
      return;
    }
  }

  // A sensible default pattern
  event.respondWith(
    caches.match(event.request).then(function (response) {
      return response || fetch(event.request);
    }),
  );
});
```

…you get the picture.

### Credits

…for the lovely icons:

- [Code](http://thenounproject.com/term/code/17547/) by buzzyrobot
- [Calendar](http://thenounproject.com/term/calendar/4672/) by Scott Lewis
- [Network by](http://thenounproject.com/term/network/12676/) Ben Rizzo
- [SD](http://thenounproject.com/term/sd-card/6185/) by Thomas Le Bas
- [CPU](http://thenounproject.com/term/cpu/72043/) by iconsmind.com
- [Trash](http://thenounproject.com/term/trash/20538/) by trasnik
- [Notification](http://thenounproject.com/term/notification/32514/) by @daosme
- [Layout](http://thenounproject.com/term/layout/36872/) by Mister Pixel
- [Cloud](http://thenounproject.com/term/cloud/2788/) by P.J. Onori

And thanks to [Jeff Posnick](https://twitter.com/jeffposnick) for catching many howling errors
before I hit "publish".

### Further reading

- [Service Workers—an Introduction][sw_primer]
- [Is Service Worker ready?][is_sw_ready]—track the implementation status across the main browsers
- [JavaScript Promises—an Introduction](/promises) - guide
  to promises

[ttt]: https://jakearchibald.github.io/trained-to-thrill/
[is_sw_ready]: https://jakearchibald.github.io/isserviceworkerready/
[sw_primer]: /service-workers-cache-storage/
[caches_api]: https://developer.mozilla.org/docs/Web/API/Cache


# Offline UX design guidelines

This article provides design guidelines on how to create a great experience
on both slow networks and offline.

The quality of a network connection can be affected by a number of factors such as:

- Poor coverage of a provider.
- Extreme weather conditions.
- Power outages.
- Entering into permanent "dead zones" such as in buildings with walls that block network connections.
- Entering into temporary "dead zones" such as when traveling on a train and going through a tunnel.
- Time-boxed internet connections such as those in airports or hotels.
- Cultural practices that require limited or no internet access at specific times or days.

Your goal is to provide a good experience that lessens the impact of changes in connectivity.

## Decide what to show your users when they have a bad network connection

The first question that must be asked is what does success and failure of a network connection look
like? A successful connection is your app's normal online experience. The failure of a connection,
however, can be both the offline state of your app as well how the app behaves when there is a laggy
network.

When thinking about the success or failure of a network connection you need to ask yourself these
important UX questions:

- How long do you wait to determine the success or failure of a connection?
- What can you do while success or failure is being determined?
- What should you do in the event of failure?
- How do you inform the user of the above?

### Inform users of their current state and change of state

Inform the user of both the actions they can still take when they have a network failure and the
current state of the application. For example, a notification could say:

> You seem to have a bad network connection. Not to worry! Messages will be sent when the
> network is restored.

<figure style="display: inline-block; max-width: 45%;">
  

*The Emojoy emoji messaging app informing the user when a change in state occurs.*

  <figcaption>
    Clearly inform the user when a change in state occurs as soon as possible.
  </figcaption>
</figure>

<figure style="display: inline-block; max-width: 45%;">
  

*The I/O 2016 app informing the user when a change in state occurs.*

  <figcaption>
    The Google I/O app used a "toast" to let the user know when they were offline.
  </figcaption>
</figure>

### Inform users when the network connection improves or is restored

How you inform the user that their network connection has improved depends
on your application. Apps such as a stock market app that prioritize current information
should auto-update and notify the user as soon as possible.

It is recommended that you let the user know that your web app has been updated "in the background"
by using a visual cue such as, for example, a material design toast element. This involves detecting both the
the presence of a service worker and an update to its managed
content. You can see a code example of this <a
href="https://github.com/GoogleChrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js#L29">function
at work here</a>.

One example of this would be [Chrome Platform Status](https://chromestatus.com)
which posts a note to the user when the app has been updated.

<figure style="display: inline-block; max-width: 45%;">
  

*An example weather app.*

  <figcaption>
    Some apps, such as the weather app, need to auto-update because old data
    is not useful.
  </figcaption>
</figure>

<figure style="display: inline-block; max-width: 45%;">
  

*Chrome Status uses a toast*

  <figcaption>
    Apps such as Chrome Status let the user know
    when content has been updated via a toast.
  </figcaption>
</figure>

You may also show the last time the app was updated at all times in a prominent space. This would
be useful for a currency converter app, for example.

<figure style="display: inline-block; max-width: 45%;">
  

*The Material Money app being out-of-date.*

  <figcaption>
    Material Money caches rates…
  </figcaption>
</figure>

<figure style="display: inline-block; max-width: 45%;">
  

*Material money has been updated*

  <figcaption>
    …and notifies the user when the app has been updated.
  </figcaption>
</figure>

Applications such as news apps could show a simple tap-to-update notification
informing the user of new content. Auto-updating would cause users to lose
their place.

<figure style="display: inline-block; max-width: 45%;">
  

*An example news app, Tailpiece in its normal state*

  <figcaption>
    Tailpiece, an online newspaper, will auto-download the latest news…
  </figcaption>
</figure>

<figure style="display: inline-block; max-width: 45%;">
  

*Example news app, Tailpiece when its ready to be updated*

  <figcaption>
    …but allow users to refresh manually so they do not lose their place in an article.
  </figcaption>
</figure>

### Update the UI to reflect the current contextual state

Each bit of UI may have its own context and functionality that will change depending on if it
requires a successful connection. One example would be an e&#8209;commerce site that can be browsed
offline. The Buy button and pricing would be disabled until a connection is reestablished.

Other forms of contextual states could include data. For example, the financial application
Robinhood allows users to buy stock and uses color and graphics to notify the user when the market
is open. The whole interface turns white and then grays out when the market closes. When the value
of stock increases or decreases, each individual stock widget turns green or red depending on its
state.

### Educate the user so they understand what the offline model is

Offline is a new mental model for everyone. You need to educate your users about what changes will
occur when they don't have a connection. Inform them of where large data is saved and give them
settings to change the default behavior. Make sure you use multiple UI design components such as
informative language, icons, notifications, color, and imagery to convey these ideas collectively
rather than relying on a single design choice, such as an icon on its own, to tell the whole story.

## Provide an offline experience by default

If your app doesn't require much data, then cache that data by default. Users can become
increasingly frustrated if they can only access their data with a network connection. Try to make
the experience as stable as possible. An unstable connection will make your app feel untrustworthy,
where an app that lessens the impact of a network failure will feel magical to the user.

News sites could benefit from auto-downloading and auto-saving the latest news so a
user could read today's news without a connection, perhaps downloading the text without the article
images. Also, adapt to the user's behavior. For example, if the sports section is what they typically view,
make that the priority download.

<figure style="display: inline-block; max-width: 45%;">
  

*Tailpiece informs the user that they are offline with various design widgets.*

  <figcaption>
    If the device is offline, Tailpiece will notify the user with a status message…
  </figcaption>
</figure>

<figure style="display: inline-block; max-width: 45%;">
  

*Tailpiece has a visual indicator that shows what sections are ready for offline use.*

  <figcaption>
    …letting them know that they can at least partially still use the app.
  </figcaption>
</figure>

  When it comes to communicating an app's status, saying "The network is down" sends the message
  that the app's network is experiencing problems, whereas "You are disconnected" makes it clearer
  to the user that the problem is on their end.

## Inform the user when the app is ready for offline consumption

When a web app first loads you need to indicate to the user if it is ready for offline use. Do this
with a
[widget that provides brief feedback](https://material.io/components/snackbars)
about an operation through a message at the bottom of the screen such as, for example, when a section has
been synced or a data file has downloaded.

Again think of the language you are using to make sure it is fit for your audience. Ensure the
messaging is the same in all instances where it's used. The term offline is generally
misunderstood by a non-technical audience so use action-based language that your audience can relate
to.

<figure style="display: inline-block; max-width: 45%;">
  

*I/O app offline.*

  <figcaption>
    The Google I/O 2016 app notified the user when
    the app is ready for offline use…
  </figcaption>
</figure>

<figure style="display: inline-block; max-width: 45%;">
  

*Chrome Status site is offline.*

  <figcaption>
    …and so does the Chrome Platform Status site, which includes information about the occupied storage.
  </figcaption>
</figure>

### Make 'save for offline' an obvious part of the interface for data-heavy apps

If an application uses large amounts of data, make sure that there is a switch or pin to add an item
for offline use rather than auto-downloading, unless a user has specifically asked for this behavior
via a settings menu. Make sure that the pin or download UI is not obscured by other UI elements and
that the feature is obvious to the user.

One example would be a music player that requires large data files. The user is aware of the
associated data cost, but may also want to use the player offline.
Downloading music for later use requires the user to plan ahead, so education about this may be
required during onboarding.

### Clarify what is available offline

Be clear as to the option you are providing. You may need to show a tab or setting that shows an
"offline library" or [content index](/content-indexing-api/),
so the user can easily see what they have stored on their phone and what needs to
be saved. Make sure the settings are concise and be clear where the data will be stored and who has
access to it.

### Show the actual cost of an action

Many users equate offline capability with 'downloading'. Users in countries where network
connections regularly fail or aren't available often share content with other users, or save content
for offline use when they have connectivity.

Users on data plans may avoid downloading large files for fear of cost, so you may also want to
display an associated cost so users can make an active comparison for a specific file or task. For
example, if the music app above could detect if the user is on a data plan and show the file size so
that users can see the cost of a file.

### Help prevent hacked experiences

Often users hack an experience without realizing they are doing it. For example before cloud-based
file sharing web apps, it was common for users to save large files and attach them to emails so
they could carry on editing from a different device. It is important not to be pulled into their
hacked experience but rather look at what they are trying to achieve. In other words instead of
thinking of how you can make attaching a large file more user-friendly, solve the problem of
sharing large files across multiple devices.

## Make experiences transferable from one device to another

When building for flaky networks, try to sync as soon as the connection improves
so that the experience is transferable. For example, imagine a travel app losing
a network connection mid-way through a booking. When the connection is reestablished, the app syncs
with the user's account allowing them to continue their booking on their desktop device. Not being
able to transfer experiences can be incredibly jarring to users.

Inform the user of the current state of their data. For example, you could show whether the
app has synced. Educate them where possible but try not to overburden them with messaging.

## Create inclusive design experiences

When designing seek to be inclusive by providing meaningful design devices, simple language,
standard iconography, and meaningful imagery that will guide the user to complete the action or task
rather than hinder their progress.

### Use simple, concise language

Good UX is not just about a well designed interface. It includes the path a user takes as well as
the words used in the app. Avoid tech jargon when explaining the state of the app or
individual UI components. Consider that the phrase "app offline" might not convey to the user the
current state of the app.

<div class="switcher">

  <figure style="display: inline-block; max-width: 45%;">
    

*A service worker icon is a bad example.*

    <figcaption>
      Avoid terms that aren't intelligible to non-technical users.
    </figcaption>
  </figure>

  <figure style="display: inline-block; max-width: 45%;">
    

*A download icon is a good example.*

    <figcaption>
      Use language and imagery that describe the action.
    </figcaption>
  </figure>

</div>

### Use multiple design devices to create accessible user experiences

Use language, color, and visual components to demonstrate a change of state or current status.
Solely using color to demonstrate state may not be noticed by the user and may be inaccessible to
users with visual disabilities. Also, the instinct for designers is to use grayed UI
to represent offline, but this can have a loaded meaning on the web. Grayed UI such as input elements
on a form also means that an element is disabled. This can cause confusion if you *only*
use color to depict state.

To prevent misunderstandings, express different states to the user in multiple ways, for example
with color, labels, and UI components.

<div class="switcher">

  <figure style="display: inline-block; max-width: 45%;">
    

*A bad example only using color.*

    <figcaption>
      Avoid using color as the sole means to describe what is happening.
    </figcaption>
  </figure>

  <figure style="display: inline-block; max-width: 45%;">
    

*A good example that uses color and text to show an error.*

    <figcaption>
      Use a mixture of design elements to convey meaning.
    </figcaption>
  </figure>

</div>

### Use icons that convey meaning

Make sure that information is conveyed correctly with meaningful text labels as well as icons. Icons
alone can be problematic, since the concept of offline on the web is relatively new. Users may
misunderstand icons used on their own. For example, using a floppy disc for save makes sense to an
older generation but young users who have never seen a floppy disc may be confused by the metaphor.
Likewise, the 'hamburger' menu icon has been known to confuse users when presented without a label.

When introducing an offline icon try to remain consistent with industry standard visuals (when they
exist) as well as providing a text label and description. For example, saving for offline might be
a typical download icon or perhaps if the action involves syncing it could be a syncing icon. Some
actions may be interpreted as saving for offline rather than demonstrating a network's status. Think
of the action you are trying to convey rather than presenting the user with an abstract concept. For
example, save or download data would be action-based.

*Various icon examples that convey offline*

Offline can mean a number of things depending on the context, such as download, export, pin, etc.
For more inspiration checkout the
[Material Design icon set](https://material.io/resources/icons/).

### Use skeleton layouts with other feedback mechanisms

A skeleton layout is essentially a wireframe version of your app that displays while content is
being loaded. This helps demonstrate to the user that content is about to be loaded. Consider also
using a preloader UI as well, with a text label informing the user that the app is loading. One
example would be to pulsate the wireframe content giving the app the feeling that it is alive and
loading. This reassures the user that something is happening and helps prevent resubmissions or
refreshes of your app.

<figure style="display: inline-block; max-width: 45%;">
  

*A skeleton layout example.*

  <figcaption>
    A skeleton placeholder layout is shown during the download of the article…
  </figcaption>
</figure>
<figure style="display: inline-block; max-width: 45%;">
  

*A loaded article example.*

  <figcaption>
    …that gets replaced with the real contents once the download finishes.
  </figcaption>
</figure>

### Don't block content

In some applications, a user might trigger an action such as creating a new document. Some apps will
try to connect to a server in order to sync the new document and to demonstrate this they display an
intrusive loading modal dialog that covers the entire screen. This may work fine if the user has a
stable network connection, but if the network is unstable they won't be able to escape from this
action and the UI effectively blocks them from doing anything else. Network requests that block
content should be avoided. Allow the user to continue to browse your app and queue tasks that will
be performed and synced once the connection has improved.

Demonstrate the state of an action by providing your users with feedback. For example, if a user is
editing a doc, consider changing the feedback design so it is visibly different from when they are
online but still shows that their file was "saved" and will sync when they have a network
connection. This will educate the user about the different states available and reassure them that
their task or action has been stored. This has the added benefit of the user growing more confident
using your application.

## Design for the next billion

In many regions, low-end devices are commonplace, connectivity is unreliable and, for many users,
data is unaffordable. You will need to earn user trust by being transparent and frugal with data.
Think about ways to help users on poor connections and simplify the interface to help speed up
tasks. Always try to ask users before downloading data-heavy content.

Offer low bandwidth options for users on laggy connections. So if the network connection is slow,
provide small assets. Offer an option to choose high or low quality assets.

## Conclusion

Education is key to offline UX as users are unfamiliar with these concepts. Try to create associations
with things that are familiar, e.g downloading for later use is the same as offlining data.

When designing for unstable network connections, remember these guidelines:

- Design for the success, failure, and instability of a network connection.
- Data may be expensive, so be considerate to the user.
- For most users globally, the tech environment is almost exclusively mobile.
- Low-end devices are commonplace, with limited storage, memory, and processing power, and small
  displays and lower touchscreen quality. Make sure performance is a part of your design process.
- Allow users to browse your application when they are offline.
- Inform users of their current state and of changes in states.
- Try to provide offline by default if your app doesn't require much data.
- If the app is data-heavy, educate users about how they can download for offline use.
- Make experiences transferable between devices.
- Use language, icons, imagery, typography, and color together to express ideas to the user.
- Provide reassurance and feedback to help the user.


# Precaching with Workbox

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 "precaching".
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.

## Why should you use Workbox?

Using Workbox for precaching is optional. You can write your own code to
[precache critical assets when the service worker is installing](/learn/pwa/caching/).
The primary benefit of using Workbox is its out-of-the-box version control.
You'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.

## Precache manifests

Precaching is driven by a list of URLs and associated versioning information for
each URL. Taken together, this information is known as a
[**precache manifest**](https://developer.chrome.com/docs/workbox/modules/workbox-precaching/#explanation-of-the-precache-list).
The manifest is the "source of truth" 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 [Workbox](https://developer.chrome.com/docs/workbox/),
looks something like:

```js
[{
  url: 'app.abcd1234.js'
}, {
  url: 'offline.svg',
  revision: '7836745f'
}, {
  url: 'index.html',
  revision: '518747aa'
}]
```

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 (`app` is the actual file name, and
`.abcd1234` contains the versioning information, right before the file extension
`.js`). Workbox'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's
build tools create a separate `revision` field, containing a hash of the local
file's contents.

## Serving precached resources

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
`fetch` 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
[**cache-first strategy**](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#cache-first-cache-falling-back-to-network).

## Efficient updates

A precache manifest provides an exact representation of the expected cache
state; if a URL/revision combination in the manifest changes, a service worker
_knows_ 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
[update check](/service-worker-lifecycle/#updates),
to determine which precached URLs need to be refreshed.

## Updates to precached resources

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 "source of truth" for your
precache state at any point in time.

If there's an existing URL with a new revision field, or if any URLs have been
added or dropped from the manifest, that's a sign to your service worker that
updates need to be performed, as part of the
[`install`](/service-worker-lifecycle/#install)
and
[`activate`](/service-worker-lifecycle/#activate)
event handlers. For instance, if you've made some changes to your site and
rebuilt, your latest precache manifest might have undergone the following
changes:

```js
[{
  url: 'app.ffaa4455.js'
}, {
  url: 'offline.svg',
  revision: '7836745f'
}, {
  url: 'index.html',
  revision: '518747aa'
}]
```

Each of these changes tells your service worker that new requests need to be
made to update previously cached entries (`'offline.svg'` and `'index.html'`)
and cache new URLs (`'app.ffaa4455.js'`), as well as deletions to clean up URLs
that are no longer used (`'app.abcd1234.js'`).

## True "app store" install experience

Another benefit of precaching is that you can give your users an experience that
would otherwise be difficult to achieve outside of an "app store"
installation. Once a user visits any page on your web app for the first time,
you can potentially precache _all_ of the URLs needed to display _any_ of your
web app's views ahead of time, without having to wait until they visit each
individual view.

When a user installs an app, they expect every part to display quickly,
not just the views that they've gone to in the past. Precaching brings that same
experience to web apps.

## Which of your assets should be precached?

Refer back to the [Identify what's being
loaded](/identify-resources-via-network-panel/) 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's loaded early on and is crucial to
displaying the basic structure of a given page.

It's preferable to avoid precaching media or other assets that are loaded later
(unless crucial for your web app's functionality). Instead, use a [runtime
caching strategy](/runtime-caching-with-workbox/) to ensure these
assets are cached-as-you-go.

Always keep in mind that precaching involves using network bandwidth and storage
on a user's device (just like installing an app from an app store does).
It's up to you as the developer to precache judiciously, and avoid a bloated
precache manifest.

Workbox'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.

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.

# Resilient search experiences



https://www.youtube.com/watch?v=fhqCwDP69PI

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 "search" button, and while waiting for the results, the connection is lost, leading to the standard browser offline page.

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.

To provide a more resilient search experience in these cases you can use the [Background Sync API](https://developer.chrome.com/blog/background-sync/), which persists failed queries so they can be retried once the connection is recovered. This technique, in combination with [Web Push Notifications](/push-notifications/) lets you inform the user of the search results, allowing you to keep them engaged with your service.

  Try the [Building resilient search experiences with Workbox](/codelab-building-resilient-search-experiences)
  for a hands-on demonstration of the ideas explained in this guide.

## Production case

For concrete application of this technique let'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.

<figure>
  

*A screenshot of the background retry interface in Google Search.*

</figure>

When the user performs a search, the service worker allows the query to be deferred and sent to Google's servers as soon as the device goes back online by using the [Background Sync API](https://developer.chrome.com/blog/background-sync/), and to inform the user of the result by using the [Push API](https://developer.mozilla.org/docs/Web/API/Push_API).

*A screenshot of the offline flow in Google Search.*

Service workers allow Google Search to provide a [meaningful offline experience](/google-search-sw/#meaningful-offline-experience) and keep the user engaged, letting them complete their task.

## Implement resilient search experiences with Workbox

While Google Search implements this functionality without using Workbox, the [Workbox library](https://developer.chrome.com/docs/workbox/) makes it easier by providing a [Background Sync module](https://developer.chrome.com/docs/workbox/modules/workbox-background-sync/), which takes care of many implementation details for us.

*A service worker and a cache object communicating with each other.*

To implement a resilient search experience in Workbox, first, import the following modules in your service worker:

```javascript
import {BackgroundSyncPlugin} from 'workbox-background-sync';
import {registerRoute} from 'workbox-routing';
import {NetworkOnly} from 'workbox-strategies';
```

Next, create an instance of the [workbox.backgroundSync plugin](https://developer.chrome.com/docs/workbox/reference/workbox-background-sync/#type-BackgroundSyncPlugin), to automatically add failed requests to a queue, so they can be retried later:

```javascript
const bgSyncPlugin = new workbox.backgroundSync.Plugin('offlineQueryQueue', {
  maxRetentionTime: 60,
  onSync: async ({queue}) => {
    let entry;
    while ((entry = await queue.shiftRequest())) {
      try {
        const response = await fetch(entry.request);
        const cache = await caches.open('offline-search-responses');
        const offlineUrl = `${entry.request.url}&notification=true`;
        cache.put(offlineUrl, response);
        showNotification(offlineUrl);
      } catch (error) {
        await this.unshiftRequest(entry);
        throw error;
      }
    }
  },
});
```

The plugin receives the following parameters:

- `offlineQueryQueue`: The name of the queue that will be used to persist the failed requests in [IndexedDB](https://developer.mozilla.org/docs/Web/API/IndexedDB_API).
- `maxRetentionTime`: The amount of time in minutes a request may be retried, after which point they will be discarded.
- `onSync`: The callback that will be triggered when the connection is recovered. At that point, each failed request can be dequeued and processed, by calling `queue.shiftRequest()`.

Finally, define a [networkOnly](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#network-only) runtime caching strategy for requests to the search URL (e.g. `/search_action`) and pass it the `bgSyncPlugin` defined previously:

```javascript
workbox.routing.registerRoute(
  matchSearchUrl,
  new workbox.strategies.NetworkOnly({
        plugins: [bgSyncPlugin]
  })
);
```

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.

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.

## Conclusion

In this article you learned how to implement a search experience capable of responding gracefully to offline scenarios, by combining the [Background Sync API](https://developer.chrome.com/blog/background-sync/) and the [Push API](https://developer.mozilla.org/docs/Web/API/Push_API).
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.

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.


# Runtime caching with Workbox

Runtime caching refers to gradually adding responses to a cache "as you go".
While runtime caching doesn't help with the reliability of the _current_
request, it can help make _future_ requests for the same URL more reliable.

The browser's HTTP cache is an example of runtime caching; it'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.

## Getting strategic

As opposed to [precaching](/precache-with-workbox/) (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:

+  Network-first
+  Cache-first
+  Stale-while-revalidate

### Network-first

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.

*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.*

If the network request fails entirely, or
[takes too long](https://developer.chrome.com/docs/workbox/forcing-a-network-timeout/)
to return a response, then the most recent response from the cache is returned
instead.

### Cache-first

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's a cached response available. If there is,
that response is returned to the web app.

If there'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'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.

*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.*

### Stale-while-revalidate

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.

In the meantime, regardless of whether there was a cache match, your service
worker also fires off a network request to get back a "fresh" 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.

*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.*

### Why should you use Workbox?

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
[strategies library](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/),
ready for you to drop in to your service worker.

Workbox also provides versioning support, allowing you to automatically
[expire](https://developer.chrome.com/docs/workbox/modules/workbox-expiration/)
cached entries, or notify your web app when
[updates](https://developer.chrome.com/docs/workbox/modules/workbox-broadcast-update/)
to a previously cached entry occur.

## Which of your assets should be cached, with which strategies?

Runtime caching can be viewed as a complement to precaching. If all of your
assets are already being precached, then you're done—there's nothing that needs
to be cached at runtime. Chances are, for any relatively complex web app, you're
not going to be precaching _everything_ though.

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'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.

### Use stale-while-revalidate to prioritize reliability over freshness

Since a stale-while-revalidate strategy returns a cached response almost
immediately—after the cache has been populated via the first request—you'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 _immediately_ is key, even
if it's an older value.

### Use network-first to prioritize freshness over reliability

In some sense, using a network-first strategy is admitting defeat in your battle
against the network—it'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.

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 _something_, even if it's a potentially stale response. You won't be
reliably fast, but at least you'll be reliable while offline.

### Use cache-first for versioned URLs

In a cache-first strategy, once an entry is cached, it'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
`Cache-Control: max-age=31536000` response header.


# Service worker caching and HTTP caching

While service workers and PWAs are becoming standards of modern web applications, resource caching
has become more complex than ever. This article covers the big picture of browser caching,
including:

+   The use cases of and differences between service worker caching and HTTP caching.
+   The pros and cons of different service worker caching expiry strategies compared to regular
    HTTP caching strategies.

## Overview of caching flow

At a high-level, a browser follows the caching order below when it requests a resource:

1.  **Service worker cache**: The service worker checks if the resource is in its cache and
    decides whether to return the resource itself based on its programmed caching strategies. Note
    that this does not happen automatically. You need to create a fetch event handler in your
    service worker and intercept network requests so that the requests are served from the service
    worker's cache rather than the network.
1.  **HTTP cache (also known as the browser cache)**: If the resource is found in the [HTTP
    Cache](/http-cache) and has not yet expired, the browser automatically uses the
    resource from the  HTTP cache.
1.  **Server-side:** If nothing is found in the service worker cache or the HTTP cache, the
    browser goes to the network to request the resource. If the resource isn't cached in a CDN, the
    request must go all the way back to the origin server.

*Caching flow*

Note that some browsers like Chrome have a **memory cache** layer in front of the service
worker cache. The details of the memory cache depend on each browser's implementation.
Unfortunately, there is no clear specification for this part yet.

## Caching layers

### Service worker caching

A service worker intercepts network-type HTTP requests and uses a
[caching strategy](/offline-cookbook/#serving-suggestions)
to determine what resources should be returned to the browser. The service worker cache and the HTTP
cache serve the same general purpose, but the service worker cache offers more caching capabilities,
such as fine-grained control over exactly what is cached and how caching is done.

#### Controlling the service worker cache

A service worker intercepts HTTP requests with [event
listeners](https://github.com/mdn/sw-test/blob/gh-pages/sw.js#L19) (usually the `fetch` event). This
[code snippet](https://github.com/mdn/sw-test/blob/gh-pages/sw.js#L19) demonstrates the logic of a
[Cache-First](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/#cache-first-cache-falling-back-to-network)
caching strategy.

*A diagram showing how service workers intercept HTTP requests*

It's highly recommended to use [Workbox](https://developer.chrome.com/docs/workbox/) to avoid
reinventing the wheel. For example, you can
[register resource URL paths with a single line of regex code](https://developer.chrome.com/docs/workbox/modules/workbox-routing/#how-to-register-a-regular-expression-route).

```js
import {registerRoute} from 'workbox-routing';

registerRoute(new RegExp('styles/.*\\.css'), callbackHandler);
```

#### Service worker caching strategies and use cases

The next table outlines common service worker caching strategies and when each strategy is useful.

<table>
<thead>
<tr>
<th><strong>Strategies</strong></th>
<th><strong>Freshness rationale</strong></th>
<th><strong>Use cases</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong><a
href="https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook#network-only">Network
only</strong></a></td>
<td>The content has to be up-to-date at all times.</td>
<td><ul><li>Payments and checkouts</li>
<li>Balance statements</li>
</ul>
</td>
</tr>
<tr>
<td><strong><a
href="https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook#network-falling-back-to-cache">Network
falling back to cache </strong></a></td>
<td>It's preferable to serve the fresh content. However if the network fails or is unstable, it's
acceptable to serve slightly old content.</td>
<td><ul><li>Timely data</li>
<li>Prices and rates (requires disclaimers)</li>
<li>Order statuses</li>
</ul>
</td>
</tr>
<tr>
<td><strong><a
href="/stale-while-revalidate/">Stale-while-revalidate</strong></a>
</td>
<td>It's okay to serve cached content right away, but updated cached content should be used in the
future.</td>
<td><ul><li>News feeds</li>
<li>Product listing pages</li>
<li>Messages</li>
</ul>
</td>
</tr>
<tr>
<td><strong><a href="https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook#cache-then-network">Cache first, fall back to network</a></strong></td>
<td>The content is non-critical and can be served from the cache for performance gains, but the
service worker should occasionally check for updates.</td>
<td><ul><li>App shells</li>
<li>Common resources</li>
</ul>
</td>
</tr>
<tr>
<td><strong><a href="https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook#cache-only">Cache only</a></strong></td>
<td>The content rarely changes.</td>
<td><ul><li>Static content</li>
</ul>
</td>
</tr>
</tbody>
</table>

#### Additional benefits of service worker caching

In addition to fine-grained control of caching logic, service worker caching also provides:

+   **More memory and storage space for your origin:** The browser allocates HTTP cache
    resources on a per-[origin](/same-site-same-origin/#origin) basis. In other
    words, if you have multiple subdomains, they all share the same HTTP cache. There is no
    guarantee that the content of your origin/domain stays in the HTTP cache for a long time. For
    example, a user may purge the cache by manually cleaning up from a browser's settings UI, or
    triggering a hard-reload on a page. With a service worker cache you have a much higher
    likelihood that your cached content stays cached. See [Persistent
    storage](/persistent-storage/) to learn more.
+   **Higher flexibility with flaky networks or offline experiences:** With the HTTP cache you
    only have a binary choice: either the resource is cached, or not. With service worker caching
    you can mitigate little "hiccups" much easier (with the "stale-while-revalidate" strategy),
    offer a complete offline experience (with the "Cache only" strategy) or even something in
    between, like customized UIs with parts of the page coming from the service worker cache and
    some parts excluded (with the "Set catch handler" strategy) where appropriate.

### HTTP caching

The first time a browser loads a web page and related resources, it stores these resources in its
HTTP cache. The HTTP cache is usually enabled automatically by browsers, unless it has been
explicitly disabled by the end user.

Using HTTP caching means relying on the server to determine when to cache a resource and for how
long.

#### Control HTTP cache expiry with HTTP response headers

When a server responds to a browser request for a resource, the server uses HTTP response headers to
tell a browser how long it should cache the resource. See [Response headers: configure your web
server](/http-cache/#response-headers) to learn more.

#### HTTP caching strategies and use cases

HTTP caching is much simpler than service worker caching, because HTTP caching only deals with
time-based (TTL) resource expiration logic. See
[Which response header values should you use?](/http-cache/#response-header-strategies)
and [Summary](/http-cache/#summary) to learn more about HTTP caching strategies.

## Designing your cache expiry logic

This section explains the pros and cons of using consistent expiry logic across the service worker
cache and HTTP cache layers, as well as the pros and cons of separate expiry logic across these
layers.

The Glitch below demonstrates how service worker caching and HTTP caching work in action across
different scenarios:

https://glitch.com/edit/#!/compare-sw-and-http-caching

### Consistent expiry logic for all cache layers

To demonstrate the pros and cons, we'll look at 3 scenarios: long-term, medium-term, and
short-term.

<table>
<thead>
<tr>
<th>Scenarios</th>
<th>Long-term caching</th>
<th>Medium-term caching</th>
<th>Short-term caching</th>
</tr>
</thead>
<tbody>
<tr>
<td>Service worker caching strategy</td>
<td>Cache, falling back to network</td>
<td>Stale-while-revalidate</td>
<td>Network falling back to cache</td>
</tr>
<tr>
<td>Service worker cache TTL</td>
<td><strong>30 days</strong></td>
<td><strong>1 day</strong></td>
<td><strong>10 mins</strong></td>
</tr>
<tr>
<td>HTTP cache max-age</td>
<td><strong>30 days</strong></td>
<td><strong>1 day</strong></td>
<td><strong>10 mins</strong></td>
</tr>
</tbody>
</table>

#### Scenario: Long-term caching (Cache, falling back to network)

+   When a cached resource is valid (<= 30 days): The service worker returns the cached
    resource immediately without going to the network.
+   When a cached resource is expired (> 30 days): The service worker goes to the network to
    fetch the resource. The browser doesn't have a copy of the resource in its HTTP cache, so it
    goes server-side for the resource.

Con: In this scenario, HTTP caching provides less value because the browser will always
pass the request to the server-side when the cache expires in the service worker.

#### Scenario: Medium-term caching (Stale-while-revalidate)

+   When a cached resource is valid (<= 1 day): The service worker returns the cached
    resource immediately, and goes to the network to fetch the resource. The browser has a copy of
    the resource in its HTTP cache, so it returns that copy to the service worker.
+   When a cached resource is expired (> 1 day): The service worker returns the cached
    resource immediately, and goes to the network to fetch the resource. The browser doesn't have a
    copy of the resource in its HTTP cache, so it goes server-side to fetch the resource.

Con: The service worker requires additional cache-busting to override the HTTP cache in
order to make the most of the "revalidate" step.

#### Scenario: Short-term caching (Network falling back to cache)

+   When a cached resource is valid (<= 10 mins): The service worker goes to the network
    to fetch the resource. The browser has a copy of the resource in its HTTP cache so it returns
    that to the service worker without going server-side.
+   When a cached resource is expired (> 10 mins): The service worker returns the cached
    resource immediately, and goes to the network to fetch the resource. The browser doesn't have a
    copy of the resource in its HTTP cache, so it goes server-side to fetch the resource.

Con: Similar to the medium-term caching scenario, the service worker requires additional
cache-busting logic to override the HTTP cache in order to fetch the latest resource from the
server-side.

#### Service worker in all scenarios

In all scenarios, the service worker cache can still return cached resources when the network is
unstable. On the other hand, the HTTP cache is not reliable when the network is unstable or down.

### Different cache expiry logic at the service worker cache and HTTP layers

To demonstrate the pros and cons, we'll again look at long-term, medium-term, and short-term
scenarios.

<table>
<thead>
<tr>
<th>Scenarios</th>
<th>Long-term caching</th>
<th>Medium-term caching</th>
<th>Short-term caching</th>
</tr>
</thead>
<tbody>
<tr>
<td>Service worker caching strategy</td>
<td>Cache, falling back to network</td>
<td>Stale-while-revalidate</td>
<td>Network falling back to cache</td>
</tr>
<tr>
<td>Service worker cache TTL</td>
<td><strong>90 days</strong></td>
<td><strong>30 days</strong></td>
<td><strong>1 day</strong></td>
</tr>
<tr>
<td>HTTP cache max-age</td>
<td><strong>30 days</strong></td>
<td><strong>1 day</strong></td>
<td><strong>10 mins</strong></td>
</tr>
</tbody>
</table>

#### Scenario: Long-term caching (Cache, falling back to network)

+   When a cached resource is valid in the service worker cache (<= 90 days): The service
    worker returns the cached resource immediately.
+   When a cached resource is expired in the service worker cache (> 90 days): The service
    worker goes to the network to fetch the resource. The browser doesn't have a copy of the
    resource in its HTTP cache, so it goes server-side.

Pros and cons:

+   Pro: Users experience instant response as the service worker returns cached resources
    immediately.
+   Pro: The service worker has more fine-grained control of when to use its cache and when
    to request new versions of resources.
+   Con: A well-defined service worker caching strategy is required.

#### Scenario: Mid-term caching (Stale-while-revalidate)

+   When a cached resource is valid in the service worker cache (<= 30 days): The service
    worker returns the cached resource immediately.
+   When a cached resource is expired in the service worker cache (> 30 days): The service
    worker goes to the network for the resource. The browser doesn't have a copy of the resource in
    its HTTP cache, so it goes server-side.

Pros and cons:

+   Pro: Users experience instant response as the service worker returns cached resources
    immediately.
+   Pro: The service worker can ensure that the **next** request for a given URL uses a
    fresh response from the network, thanks to the revalidation that happens "in the background."
+   Con: A well-defined service worker caching strategy is required.

#### Scenario: Short-term caching (Network falling back to cache)

+   When a cached resource is valid in the service worker cache (<= 1 day): The service
    worker goes to the network for the resource. The browser returns the resource from the HTTP
    cache if it's there. If the network is down, the service worker returns the resource from the
    service worker cache
+   When a cached resource is expired in the service worker cache (> 1 day): The service
    worker goes to the network to fetch the resource. The browser fetches the resources over the
    network as the cached version in its HTTP cache is expired.

Pros and cons:

+   Pro: When the network is unstable or down, the service worker returns cached
    resources immediately.
+   Con: The service worker requires additional cache-busting to override the HTTP Cache and
    make "Network first" requests.

## Conclusion

Given the complexity of the combination of caching scenarios, it's not possible to design one rule
that covers all cases. However, based on the findings in the previous sections, there are a few
suggestions to look at when designing your cache strategies:

+   Service worker caching logic doesn't need to be consistent with HTTP caching expiry
    logic. If possible, use longer expiry logic in the service worker to grant the service worker
    more control.
+   HTTP caching still plays an important role, but it's not reliable when the network is
    unstable or down.
+   Revisit your caching strategies for each resource to make sure your service worker caching
    strategy provides its value, without conflicting with the HTTP cache.

## Learn more

+   [Network reliability](/reliable/)
+   [Prevent unnecessary network requests with the HTTP Cache](/http-cache)
+   [HTTP cache codelab](/codelab-http-cache/)
+   [Measuring the real-world performance impact of service workers](/service-worker-perf/)
+   [Cache-Control vs. Expires](https://stackoverflow.com/questions/5799906/what-s-the-difference-between-expires-and-cache-control-headers)


# The service worker lifecycle

The lifecycle of the service worker is its most complicated part. If you don't know what it's trying to do and what the benefits are, it can feel like it's fighting you. But once you know how it works, you can deliver seamless, unobtrusive updates to users, mixing the best of web and native patterns.

This is a deep dive, but the bullets at the start of each section cover most of what you need to know.

## The intent

The intent of the lifecycle is to:

* Make offline-first possible.
* Allow a new service worker to get itself ready without disrupting the current one.
* Ensure an in-scope page is controlled by the same service worker (or no service worker) throughout.
* Ensure there's only one version of your site running at once.

That last one is pretty important. Without service workers, users can load one tab to your site, then later open another. This can result in two versions of your site running at the same time. Sometimes this is ok, but if you're dealing with storage you can easily end up with two tabs having very different opinions on how their shared storage should be managed. This can result in errors, or worse, data loss.

Users actively dislike data loss. It causes them great sadness.

## The first service worker

In brief:

* The `install` event is the first event a service worker gets, and it only happens once.
* A promise passed to `installEvent.waitUntil()` signals the duration and success or failure of your install.
* A service worker won't receive events like `fetch` and `push` until it successfully finishes installing and becomes "active".
* By default, a page's fetches won't go through a service worker unless the page request itself went through a service worker. So you'll need to refresh the page to see the effects of the service worker.
* `clients.claim()` can override this default, and take control of non-controlled pages.

<figure>
  
</figure>

Take this HTML:

```html
<!DOCTYPE html>
An image will appear here in 3 seconds:
<script>
  navigator.serviceWorker.register('/sw.js')
    .then(reg => console.log('SW registered!', reg))
    .catch(err => console.log('Boo!', err));

  setTimeout(() => {
    const img = new Image();
    img.src = '/dog.svg';
    document.body.appendChild(img);
  }, 3000);
</script>
```

It registers a service worker, and adds image of a dog after 3 seconds.

Here's its service worker, `sw.js`:

```js
self.addEventListener('install', event => {
  console.log('V1 installing…');

  // cache a cat SVG
  event.waitUntil(
    caches.open('static-v1').then(cache => cache.add('/cat.svg'))
  );
});

self.addEventListener('activate', event => {
  console.log('V1 now ready to handle fetches!');
});

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // serve the cat SVG from the cache if the request is
  // same-origin and the path is '/dog.svg'
  if (url.origin == location.origin && url.pathname == '/dog.svg') {
    event.respondWith(caches.match('/cat.svg'));
  }
});
```

It caches an image of a cat, and serves it whenever there's a request for `/dog.svg`. However, if you [run the above example](https://service-worker-lifecycle-demos.glitch.me/main/), you'll see a dog the first time you load the page. Hit refresh, and you'll see the cat.

Note: Cats are better than dogs. They just *are*.

### Scope and control

The default scope of a service worker registration is `./` relative to the script URL. This means if you register a service worker at `//example.com/foo/bar.js` it has a default scope of `//example.com/foo/`.

We call pages, workers, and shared workers `clients`. Your service worker can only control clients that are in-scope. Once a client is "controlled", its fetches go through the in-scope service worker. You can detect if a client is controlled via `navigator.serviceWorker.controller` which will be null or a service worker instance.

### Download, parse, and execute

Your very first service worker downloads when you call `.register()`. If your script fails to download, parse, or throws an error in its initial execution, the register promise rejects, and the service worker is discarded.

Chrome's DevTools shows the error in the console, and in the service worker section of the application tab:

<figure>
  

*Error displayed in service worker DevTools tab*

</figure>

### Install

The first event a service worker gets is `install`. It's triggered as soon as the worker executes, and it's only called once per service worker. If you alter your service worker script the browser considers it a different service worker, and it'll get its own `install` event. I'll cover [updates in detail later](#updates).

The `install` event is your chance to cache everything you need before being able to control clients. The promise you pass to `event.waitUntil()` lets the browser know when your install completes, and if it was successful.

If your promise rejects, this signals the install failed, and the browser throws the service worker away. It'll never control clients. This means we can't rely on `cat.svg` being present in the cache in our `fetch` events. It's a dependency.

### Activate

Once your service worker is ready to control clients and handle functional events like `push` and `sync`, you'll get an `activate` event. But that doesn't mean the page that called `.register()` will be controlled.

The first time you load [the demo](https://service-worker-lifecycle-demos.glitch.me/main/), even though `dog.svg` is requested long after the service worker activates, it doesn't handle the request, and you still see the image of the dog. The default is *consistency*, if your page loads without a service worker, neither will its subresources. If you load [the demo](https://service-worker-lifecycle-demos.glitch.me/main/) a second time (in other words, refresh the page), it'll be controlled. Both the page and the image will go through `fetch` events, and you'll see a cat instead.

### clients.claim

You can take control of uncontrolled clients by calling `clients.claim()` within your service worker once it's activated.

Here's [a variation of the demo above](https://service-worker-lifecycle-demos.glitch.me/claimed/) which calls `clients.claim()` in its `activate` event. You *should* see a cat the first time. I say "should", because this is timing sensitive. You'll only see a cat if the service worker activates and `clients.claim()` takes effect before the image tries to load.

If you use your service worker to load pages differently than they'd load via the network, `clients.claim()` can be troublesome, as your service worker ends up controlling some clients that loaded without it.

**Note:** I see a lot of people including `clients.claim()` as boilerplate, but I
rarely do so myself. It only really matters on the very first load, and due to
progressive enhancement the page is usually working happily without service
worker anyway.

## Updating the service worker {: #updates}

In brief:

* An update is triggered if any of the following happens:
  * A navigation to an in-scope page.
  * A functional events such as `push` and `sync`, unless there's been an update check within the previous 24 hours.
  * Calling `.register()` *only if* the service worker URL has changed. However, you should [avoid changing the worker URL](#avoid-url-change).
* Most browsers, including [Chrome 68 and later](https://developer.chrome.com/blog/fresher-sw/), default to ignoring caching headers when checking for updates of the registered service worker script. They still respect caching headers when fetching resources loaded inside a service worker via `importScripts()`. You can override this default behavior by setting the [`updateViaCache`](https://developers.google.com/web/updates/2018/06/fresher-sw/#updateviacache) option when registering your service worker.
* Your service worker is considered updated if it's byte-different to the one the browser already has. (We're extending this to include imported scripts/modules too.)
* The updated service worker is launched alongside the existing one, and gets its own `install` event.
* If your new worker has a non-ok status code (for example, 404), fails to parse, throws an error during execution, or rejects during install, the new worker is thrown away, but the current one remains active.
* Once successfully installed, the updated worker will `wait` until the existing worker is controlling zero clients. (Note that clients overlap during a refresh.)
* `self.skipWaiting()` prevents the waiting, meaning the service worker activates as soon as it's finished installing.

<figure>
  
</figure>

Let's say we changed our service worker script to respond with a picture of a horse rather than a cat:

```js
const expectedCaches = ['static-v2'];

self.addEventListener('install', event => {
  console.log('V2 installing…');

  // cache a horse SVG into a new cache, static-v2
  event.waitUntil(
    caches.open('static-v2').then(cache => cache.add('/horse.svg'))
  );
});

self.addEventListener('activate', event => {
  // delete any caches that aren't in expectedCaches
  // which will get rid of static-v1
  event.waitUntil(
    caches.keys().then(keys => Promise.all(
      keys.map(key => {
        if (!expectedCaches.includes(key)) {
          return caches.delete(key);
        }
      })
    )).then(() => {
      console.log('V2 now ready to handle fetches!');
    })
  );
});

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);

  // serve the horse SVG from the cache if the request is
  // same-origin and the path is '/dog.svg'
  if (url.origin == location.origin && url.pathname == '/dog.svg') {
    event.respondWith(caches.match('/horse.svg'));
  }
});
```


**Note:** I have no strong opinions on horses.

[Check out a demo of the above](https://service-worker-lifecycle-demos.glitch.me/main/v2.html). You should still see an image of a cat. Here's why…

### Install

Note that I've changed the cache name from `static-v1` to `static-v2`. This means I can set up the new cache without overwriting things in the current one, which the old service worker is still using.

This patterns creates version-specific caches, akin to assets a native app would bundle with its executable. You may also have caches that aren't version specific, such as `avatars`.

### Waiting

After it's successfully installed, the updated service worker delays activating until the existing service worker is no longer controlling clients. This state is called "waiting", and it's how the browser ensures that only one version of your service worker is running at a time.

If you ran [the updated demo](https://service-worker-lifecycle-demos.glitch.me/main/v2.html), you should still see a picture of a cat, because the V2 worker hasn't yet activated. You can see the new service worker waiting in the "Application" tab of DevTools:

<figure>
  

*DevTools showing new service worker waiting*

</figure>

Even if you only have one tab open to the demo, refreshing the page isn't enough to let the new version take over. This is due to how browser navigations work. When you navigate, the current page doesn't go away until the response headers have been received, and even then the current page may stay if the response has a `Content-Disposition` header. Because of this overlap, the current service worker is always controlling a client during a refresh.

To get the update, close or navigate away from all tabs using the current service worker. Then, when you [navigate to the demo again](https://service-worker-lifecycle-demos.glitch.me/main/v2.html), you should see the horse.

This pattern is similar to how Chrome updates. Updates to Chrome download in the background, but don't apply until Chrome restarts. In the mean time, you can continue to use the current version without disruption. However, this is a pain during development, but DevTools has ways to make it easier, which I'll cover [later in this article](#devtools).

### Activate

This fires once the old service worker is gone, and your new service worker is able to control clients. This is the ideal time to do stuff that you couldn't do while the old worker was still in use, such as migrating databases and clearing caches.

In the demo above, I maintain a list of caches that I expect to be there, and in the `activate` event I get rid of any others, which removes the old `static-v1` cache.

You may not be updating from the previous version. It may be a service worker many versions old.

If you pass a promise to `event.waitUntil()` it'll buffer functional events (`fetch`, `push`, `sync` etc.) until the promise resolves. So when your `fetch` event fires, the activation is fully complete.

The cache storage API is "origin storage" (like localStorage, and IndexedDB). If you run many sites on the same origin (for example, `yourname.github.io/myapp`), be careful that you don't delete caches for your other sites. To avoid this, give your cache names a prefix unique to the current site, eg `myapp-static-v1`, and don't touch caches unless they begin with `myapp-`.

### Skip the waiting phase

The waiting phase means you're only running one version of your site at once, but if you don't need that feature, you can make your new service worker activate sooner by calling `self.skipWaiting()`.

This causes your service worker to kick out the current active worker and activate itself as soon as it enters the waiting phase (or immediately if it's already in the waiting phase). It *doesn't* cause your worker to skip installing, just waiting.

It doesn't really matter when you call `skipWaiting()`, as long as it's during or before waiting. It's pretty common to call it in the `install` event:

```js
self.addEventListener('install', event => {
  self.skipWaiting();

  event.waitUntil(
    // caching etc
  );
});
```

But you may want to call it as a results of a `postMessage()` to the service worker. As in, you want to `skipWaiting()` following a user interaction.

[Here's a demo that uses `skipWaiting()`](https://service-worker-lifecycle-demos.glitch.me/main/v3.html). You should see a picture of a cow without having to navigate away. Like `clients.claim()` it's a race, so you'll only see the cow if the new service worker fetches, installs and activates before the page tries to load the image.

`skipWaiting()` means that your new service worker is likely controlling pages that were loaded with an older version. This means some of your page's fetches will have been handled by your old service worker, but your new service worker will be handling subsequent fetches. If this might break things, don't use `skipWaiting()`.

### Manual updates

As I mentioned earlier, the browser checks for updates automatically after navigations and functional events, but you can also trigger them manually:

```js
navigator.serviceWorker.register('/sw.js').then(reg => {
  // sometime later…
  reg.update();
});
```

If you expect the user to be using your site for a long time without reloading, you may want to call `update()` on an interval (such as hourly).

### Avoid changing the URL of your service worker script {: #avoid-url-change}

If you've read [my post on caching best practices](https://jakearchibald.com/2016/caching-best-practices/), you may consider giving each version of your service worker a unique URL. **Don't do this!** This is usually bad practice for service workers, just update the script at its current location.

It can land you with a problem like this:

1. `index.html` registers `sw-v1.js` as a service worker.
2. `sw-v1.js` caches and serves `index.html` so it works offline-first.
3. You update `index.html` so it registers your new and shiny `sw-v2.js`.

If you do the above, the user never gets `sw-v2.js`, because `sw-v1.js` is serving the old version of `index.html` from its cache. You've put yourself in a position where you need to update your service worker in order to update your service worker. Ew.

However, for [the demo above](https://service-worker-lifecycle-demos.glitch.me/main/v2.html), I *have* changed the URL of the service worker. This is so, for the sake of the demo, you can switch between the versions. It isn't something I'd do in production.

## Making development easy {: #devtools}

The service worker lifecycle is built with the user in mind, but during development it's a bit of a pain. Thankfully there are a few tools to help out:

### Update on reload

This one's my favourite.

<figure>
  

*DevTools showing 'update on reload'*

</figure>

This changes the lifecycle to be developer-friendly. Each navigation will:

1. Refetch the service worker.
2. Install it as a new version even if it's byte-identical, meaning your `install` event runs and your caches update.
3. Skip the waiting phase so the new service worker activates.
4. Navigate the page.

This means you'll get your updates on each navigation (including refresh) without having to reload twice or close the tab.

### Skip waiting

<figure>
  

*DevTools showing 'skip waiting'*

</figure>

If you have a worker waiting, you can hit "skip waiting" in DevTools to immediately promote it to "active".

### Shift-reload

If you force-reload the page (shift-reload) it bypasses the service worker entirely. It'll be uncontrolled. This feature is in the spec, so it works in other service-worker-supporting browsers.

## Handling updates

The service worker was designed as part of the [extensible web](https://extensiblewebmanifesto.org/). The idea is that we, as browser developers, acknowledge that we are not better at web development than web developers. And as such, we shouldn't provide narrow high-level APIs that solve a particular problem using patterns *we* like, and instead give you access to the guts of the browser and let you do it how you want, in a way that works best for *your* users.

So, to enable as many patterns as we can, the whole update cycle is observable:

```js
navigator.serviceWorker.register('/sw.js').then(reg => {
  reg.installing; // the installing worker, or undefined
  reg.waiting; // the waiting worker, or undefined
  reg.active; // the active worker, or undefined

  reg.addEventListener('updatefound', () => {
    // A wild service worker has appeared in reg.installing!
    const newWorker = reg.installing;

    newWorker.state;
    // "installing" - the install event has fired, but not yet complete
    // "installed"  - install complete
    // "activating" - the activate event has fired, but not yet complete
    // "activated"  - fully active
    // "redundant"  - discarded. Either failed install, or it's been
    //                replaced by a newer version

    newWorker.addEventListener('statechange', () => {
      // newWorker.state has changed
    });
  });
});

navigator.serviceWorker.addEventListener('controllerchange', () => {
  // This fires when the service worker controlling this page
  // changes, eg a new worker has skipped waiting and become
  // the new active worker.
});
```

## The lifecycle goes ever on

As you can see, it pays to understand the service worker lifecycle&mdash;and with that understanding, service worker behaviors should seem more logical, and less mysterious. That knowledge will give you more confidence as you deploy and update service workers.


# Service workers and the Cache Storage API

You're locked in a struggle for network reliability. The browser's HTTP cache is
your first line of defense, but as you've learned, it's only really effective
when loading versioned URLs that you've previously visited. On its own, the HTTP
cache is not enough.

Fortunately, two newer tools are available to help turn the tide in your favor:
[service workers](https://developer.mozilla.org/docs/Web/API/Service_Worker_API)
and the
[Cache Storage API](https://developer.mozilla.org/docs/Web/API/CacheStorage).
Since they're so often used together, it's worth learning about them both at the
same time.

## Service workers

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.

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.

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.

For other requests, though, you can take advantage of something more flexible
than the browser'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.

## The Cache Storage API

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's built-in [heuristics](https://httpwg.org/specs/rfc7234.html#heuristic.freshness),
the Cache
Storage API exposes a code-driven approach to caching. The Cache Storage API
is particularly useful when called from your service worker's JavaScript.

### Wait… there's another cache to think about now?

You're probably asking yourself questions like "Do I still need to configure my
HTTP headers?" and "What can I do with this new cache that wasn't possible with
the HTTP cache?" Don't worry—those are natural reactions.

It's still recommended that you configure the `Cache-Control` headers on your web
server, even when you know that you're using the Cache Storage API. You can
usually get away with setting `Cache-Control: no-cache` for unversioned URLs,
and/or `Cache-Control: max-age=31536000` for URLs that contain versioning
information, like hashes.

When populating the Cache Storage API cache, the browser
[defaults to checking for existing entries](https://jakearchibald.com/2016/caching-best-practices/#the-service-worker-the-http-cache-play-well-together-dont-make-them-fight)
in the HTTP cache, and uses those if found. If you'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're using misconfigured `Cache-Control` headers,
like specifying a long-lived cache lifetime for an unversioned URL, you can end
up
[making things worse](https://jakearchibald.com/2016/caching-best-practices/#a-service-worker-can-extend-the-life-of-these-bugs)
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.

As for what'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:

+  Use a "refresh in the background" approach to cached content, known as
    stale-while-revalidate.
+  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.
+  Compare previously cached and fresh network responses to see if
    anything's changed, and enable the user to update content (with a button,
    for example)  when data has actually been updated.

Check out [The Cache API: A quick guide](/cache-api-quick-guide/) to learn more.

### API nuts and bolts

There are some things to keep in mind about the API's design:

+  [`Request`](https://developer.mozilla.org/docs/Web/API/Request)
    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
    `'https://example.com/index.html'` as the key instead of an actual
    `Request` object, and the API will automatically handle that for you.
+  [`Response`](https://developer.mozilla.org/docs/Web/API/Response)
    objects are used as the values in these caches.
+  The `Cache-Control` header on a given `Response` 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'll be covered later on in this series.)
+  Unlike with older, synchronous APIs such as
    [`LocalStorage`](https://developer.mozilla.org/docs/Web/API/Storage/LocalStorage),
    all Cache Storage API operations are asynchronous.

## A quick detour: promises and async/await

Service workers and the Cache Storage API use
[asynchronous programming concepts](https://en.wikipedia.org/wiki/Asynchrony_(computer_programming)).
In particular, they rely heavily on promises to represent the future result of
async operations. You should familiarize yourself with
[promises](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise),
and the related
[`async`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function)/[`await`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/await)
syntax, before diving in.

[Make an application reliable by registering a service worker](/codelab-service-workers).

## Don't deploy that code… yet

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 "gotchas". 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:
[Workbox](https://developer.chrome.com/docs/workbox/).

Learn while having fun. Check out the new [Service Workies game!](https://serviceworkies.com/).


# Two-way communication with service workers

In some cases, a web app might need to establish a **two-way** communication channel between the
page and the service worker.

For example: in a podcast PWA one could build a feature to let the user [download episodes for
offline consumption](/app-like-pwas/#proactive-background-downloading) and allow the
service worker to keep the page regularly informed about the progress, so the [main
thread](https://developer.mozilla.org/docs/Glossary/Main_thread) can update the UI.

In this guide we'll explore the different ways of implementing a **two-way** communication between
the [Window](https://developer.mozilla.org/docs/Web/API/Window) and [service
worker](https://developer.mozilla.org/docs/Web/API/Service_Worker_API) context, by exploring
different APIs, the [Workbox library](https://developer.chrome.com/docs/workbox/), as well as
some advanced cases.

<figure>
  

*Diagram showing a service worker and the page exchanging messages.*

</figure>

  Check out [Workers overview](/workers-overview/) for a high-level explanation
  of when to use web workers versus service workers and the rest of the
  [Communicate with workers](/reliable/#communicate-with-workers) series
  for guides on other common use cases.

## Using Workbox {: #using-workbox }

[`workbox-window`](https://developer.chrome.com/docs/workbox/modules/workbox-window/) is a set of
modules of the [Workbox library](https://developer.chrome.com/docs/workbox/) that are intended
to run in the window context. The [`Workbox`
](https://developer.chrome.com/docs/workbox/reference/workbox-window/#type-Workbox) class provides a `messageSW()` method to send a message to the instance's registered service worker and
await a response.

The following page code creates a new `Workbox` instance and sends a message to the service worker
to obtain its version:

```javascript
const wb = new Workbox('/sw.js');
wb.register();

const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
```

The service worker implements a message listener on the other end, and responds to the registered
service worker:

```javascript
const SW_VERSION = '1.0.0';

self.addEventListener('message', (event) => {
  if (event.data.type === 'GET_VERSION') {
    event.ports[0].postMessage(SW_VERSION);
  }
});
```

Under the hood the library uses a browser API that we'll review in the next section: [Message
Channel](https://developer.mozilla.org/docs/Web/API/Channel_Messaging_API), but abstracts many
implementation details, making it easier to use, while leveraging the [wide browser
support](https://caniuse.com/mdn-api_messagechannel_port1) this API has.

<figure>
  

*Diagram showing two-way communication between page and service worker, using Workbox Window.*

</figure>

## Using Browser APIs {: #using-browser-apis }

If the Workbox library is not enough for your needs, there are several lower-level APIs available to
implement **"two-way"** communication between pages and service workers. They have some similarities
and differences:

Similarities:

- In all cases the communication starts on one end via the `postMessage()` interface and is received
  on the other end by implementing a `message` handler.
- In practice, all the available APIs allow us to implement the same use cases, but some of them
  might simplify development in some scenarios.

Differences:

- They have different ways of identifying the other side of the communication: some of them use an
  explicit reference to the other context, while others can communicate implicitly via a proxy
  object instantiated on each side.
- Browser support varies among them.

<figure>
  

*Diagram showing two-way communication between page and service worker, and the available browser APIs.*

</figure>

### Broadcast Channel API {: #broadcast-channel-api }

The [Broadcast Channel API](https://developer.mozilla.org/docs/Web/API/Broadcast_Channel_API)
allows basic communication between browsing contexts via [BroadcastChannel
objects](https://developer.mozilla.org/docs/Web/API/BroadcastChannel).

To implement it, first, each context has to instantiate a `BroadcastChannel` object with the same ID
and send and receive messages from it:

```javascript
const broadcast = new BroadcastChannel('channel-123');
```

The BroadcastChannel object exposes a `postMessage()` interface to send a message to any listening
context:

```javascript
//send message
broadcast.postMessage({ type: 'MSG_ID', });
```

Any browser context can listen to messages via the `onmessage` method of the `BroadcastChannel`
object:

```javascript
//listen to messages
broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process message...
  }
};
```

As seen, there's no explicit reference to a particular context, so there's no need of obtaining a
reference first to the service worker or any particular client.

<figure>
  

*Diagram showing two-way communication between page and service worker, using a Broadcast Channel object.*

</figure>

The disadvantage is that, at the moment of this writing, the API has support from Chrome, Firefox
and Edge, but other browsers, like Safari, [don't support it
yet](https://caniuse.com/?search=broadcastchannel).

### Client API {: #channel-api }

The [Client API](https://developer.mozilla.org/docs/Web/API/Client) allows you to obtain a
reference to all the [`WindowClient`](https://developer.mozilla.org/docs/Web/API/WindowClient) objects representing the active tabs that the service worker is controlling.

Since the page is controlled by a single service worker, it listens to and sends messages to the
active service worker directly via the `serviceWorker` interface:

```javascript
//send message
navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
});

//listen to messages
navigator.serviceWorker.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process response
  }
};
```

Similarly, the service worker listens to messages by implementing an `onmessage` listener:

```javascript
//listen to messages
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //Process message
  }
});
```

To communicate back with any of its clients, the service worker obtains an array of
[`WindowClient`](https://developer.mozilla.org/docs/Web/API/WindowClient) objects by executing
methods such as
[`Clients.matchAll()`](https://developer.mozilla.org/docs/Web/API/Clients/matchAll) and
[`Clients.get()`](https://developer.mozilla.org/docs/Web/API/Clients/get). Then it can
`postMessage()` any of them:

```javascript
//Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
  if (clients && clients.length) {
    //Respond to last focused tab
    clients[0].postMessage({type: 'MSG_ID'});
  }
});
```

<figure>
  

*Diagram showing a service worker communicating with an array of clients.*

</figure>

`Client API` is a good option to communicate easily with all the active tabs from a service worker
in a relatively straightforward way. The API is supported by [all major
browsers](https://wpt.fyi/results/service-workers/service-worker/client-id.https.html?label=experimental&label=master&aligned),
but not all its methods might be available, so make sure to check browser support before
implementing it in your site.

 In this guide we focus mostly on reliability use cases. Another interesting usage of
this API is synchronizing data across documents via a service worker. Check out [this episode of
HTTP 203](https://www.youtube.com/watch?v=9UNwHmagedE&feature=youtu.be&t=697) to know more about it.

### Message Channel {: #message-channel }

[Message Channel](https://developer.mozilla.org/docs/Web/API/Channel_Messaging_API) requires
defining and passing a port from one context to another to establish a **two-way** communication
channel.

To initialize the channel, the page instantiates a [`MessageChannel`](https://developer.mozilla.org/docs/Web/API/MessageChannel/MessageChannel) object and uses it
to send a port to the registered service worker. The page also implements an `onmessage` listener on
it to receive messages from the other context:

```javascript
const messageChannel = new MessageChannel();

//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
  messageChannel.port2,
]);

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
```

<figure>
  

*Diagram showing a page passing a port to a service worker, to establish two-way communication.*

</figure>

The service worker receives the port, saves a reference to it and uses it to send a message to the other
side:

```javascript
let communicationPort;

//Save reference to port
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PORT_INITIALIZATION') {
    communicationPort = event.ports[0];
  }
});

//Send messages
communicationPort.postMessage({type: 'MSG_ID'});
```

`MessageChannel` is currently supported by [all major
browsers](https://caniuse.com/?search=channel).

### Advanced APIs: Background Sync and Background Fetch {: #advanced-apis:-background-sync-and-background-fetch }

In this guide we explored ways of implementing **two-way** communication techniques, for relatively
simple cases, like passing a string message describing the operation to perform, or a list of URLs
to cache from one context to the other. In this section we'll explore two APIs to handle specific
scenarios: lack of connectivity and long downloads.

#### Background Sync {: #background-sync }

A chat app might want to make sure that messages are never lost due to bad connectivity. The
[Background Sync API](https://developer.chrome.com/blog/background-sync/) lets you
defer actions to be retried when the user has stable connectivity. This is useful for ensuring that
whatever the user wants to send, is actually sent.

Instead of the `postMessage()` interface, the page registers a `sync`:

```javascript
navigator.serviceWorker.ready.then(function (swRegistration) {
  return swRegistration.sync.register('myFirstSync');
});
```

The service worker then listens for the `sync` event to process the message:

```javascript
self.addEventListener('sync', function (event) {
  if (event.tag == 'myFirstSync') {
    event.waitUntil(doSomeStuff());
  }
});
```

The function `doSomeStuff()` should return a promise indicating the success/failure of whatever it's
trying to do. If it fulfills, the sync is complete. If it fails, another sync will be scheduled to
retry. Retry syncs also wait for connectivity, and employ an exponential back-off.

Once the operation has been performed, the service worker can then communicate back with the page to
update the UI, by using any of the communication APIs explored earlier.

Google search uses Background Sync to persist failed queries due to bad connectivity, and retry
them later when the user is online. Once the operation is performed, they communicate the result to
the user via a web push notification:

<figure>
  

*Diagram showing a page passing a port to a service worker, to establish two-way communication.*

</figure>

 Check out [Resilient search experiences
](/resilient-search-experiences/) to learn how to implement this feature using [Workbox Background
Sync](https://developer.chrome.com/docs/workbox/modules/workbox-background-sync/). 

#### Background Fetch {: #background-fetch }

For relatively short bits of work like sending a message, or a list of URLs to cache, the options
explored so far are a good choice. If the task takes too long the browser will kill the service
worker, otherwise it's a risk to the user's privacy and battery.

The [Background Fetch API](https://developer.chrome.com/blog/background-fetch/)
allows you to offload a long task to a service worker, like downloading movies, podcasts, or levels
of a game.

To communicate to the service worker from the page, use `backgroundFetch.fetch`, instead of
`postMessage()`:

```javascript
navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.fetch(
    'my-fetch',
    ['/ep-5.mp3', 'ep-5-artwork.jpg'],
    {
      title: 'Episode 5: Interesting things.',
      icons: [
        {
          sizes: '300x300',
          src: '/ep-5-icon.png',
          type: 'image/png',
        },
      ],
      downloadTotal: 60 * 1024 * 1024,
    },
  );
});
```

The `BackgroundFetchRegistration` object allows the page listen to the `progress` event to follow
the progress of the download:

```javascript
bgFetch.addEventListener('progress', () => {
  // If we didn't provide a total, we can't provide a %.
  if (!bgFetch.downloadTotal) return;

  const percent = Math.round(
    (bgFetch.downloaded / bgFetch.downloadTotal) * 100,
  );
  console.log(`Download progress: ${percent}%`);
});
```

<figure>
  

*Diagram showing a page passing a port to a service worker, to establish two-way communication.*

    <figcaption>The UI is updated to indicate the progress of a download (left). Thanks to service workers, the operation can continue running when all tabs have been closed (right).
    </figcaption>
</figure>

 Check out the [Background Fetch
guide](https://developer.chrome.com/blog/background-fetch/), which includes an
[example podcast app](https://bgfetch-http203.glitch.me/) along with its [Glitch
code](https://glitch.com/edit/#!/bgfetch-http203). 

## Next steps {: #next-steps }

In this guide we explored the most general case of communication between page and service workers
(bidirectional communication).

Many times, one might need only one context to communicate with the other, without receiving a
response. Check out the following guides for guidance how to implement unidirectional techniques in
your pages from and to the service worker, along with use cases and production examples:

- [Imperative caching guide](/imperative-caching-guide): Calling a service worker from the page to
  cache resources in advance (e.g. in prefetching scenarios).
- [Broadcast updates](/broadcast-updates-guide): Calling the page from the service worker to inform
  about important updates (e.g. a new version of the webapp is available).


# Workbox: your high-level service worker toolkit

Two APIs play a crucial role in building reliable web apps:
[Service Worker](https://developer.mozilla.org/docs/Web/API/Service_Worker_API)
and [Cache Storage](https://developer.mozilla.org/docs/Web/API/Cache). 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.

[Workbox](https://developer.chrome.com/docs/workbox/) 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.

### Runtime code

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
[dozen or so library modules in total](https://developer.chrome.com/docs/workbox/modules/),
that each handle a variety of specialized use cases. The most important modules
determine _whether_ the service worker will respond (known as
[routing](https://developer.chrome.com/docs/workbox/modules/workbox-routing/)),
and _how_ it will respond (known as the
[caching strategy](https://developer.chrome.com/docs/workbox/modules/workbox-strategies/)).

### Build integration

Workbox offers
[command line](https://developer.chrome.com/docs/workbox/modules/workbox-cli/),
[Node.js module](https://developer.chrome.com/docs/workbox/modules/workbox-build/),
and
[webpack plugin](https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/)
tools that provide alternative ways to accomplish two things:

+  Create a service worker script based on a set of configuration
    options. The generated service worker uses Workbox's runtime libraries
    "under the hood" to put into action the caching strategies you configure.
+  Generate a list of URLs that should be
    "[precached](https://developer.chrome.com/docs/workbox/modules/workbox-precaching/)",
    based on configurable patterns to include and exclude files generated
    during your build process.

## Why should you use Workbox?

Using Workbox when building your service worker is optional—there are a number
of guides out there that walk through
[common caching strategies](/offline-cookbook/)
from a "vanilla" service worker perspective. If you do decide to use Workbox,
here are some of its benefits.

### Cache management

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.

### Extensive logging and error reporting

When you're getting started with service workers, figuring out _why_ something
is being cached (or, equally frustrating, why it _isn't_ cached) is a challenge.
Workbox automatically detects when you're running a development version of your
website on `localhost`, and turns on debug logging in your browser's JavaScript
console.

*Workbox logging to the DevTools console*

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.

### A tested, cross-browser codebase

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.

+  The
    [`workbox-broadcast-cache-update module`](https://developer.chrome.com/docs/workbox/modules/workbox-broadcast-update/)
    uses the
    [Broadcast Channel API](https://developer.mozilla.org/docs/Web/API/Broadcast_Channel_API)
    when available, and falls back to a
    [`postMessage()`](https://developer.mozilla.org/docs/Web/API/Window/postMessage)-based
    implementation on browsers that lack support.
+  The
    [workbox-background-sync module](https://developer.chrome.com/docs/workbox/modules/workbox-background-sync/)
    uses the
    [Background Sync API](https://developer.chrome.com/blog/background-sync/)
    if possible, and if not, falls back to retrying queued events each time the
    service worker starts up.

## How should you use Workbox?

### Framework integration

If you'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:

+  [create-react-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
+  [vue-cli](https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-plugin-pwa/README.md)
+  [preact-cli](https://github.com/prateekbh/preact-cli-workbox-plugin/blob/master/README.md)
+  [Gatsby](https://www.gatsbyjs.org/packages/gatsby-plugin-offline/)
+  [Next.js](https://github.com/hanford/next-offline/blob/master/readme.md)

### Add Workbox to your existing build process

If you already have a build process for your site in place, dropping in the
appropriate
[command line](https://developer.chrome.com/docs/workbox/modules/workbox-cli/),
[Node.js module](https://developer.chrome.com/docs/workbox/modules/workbox-build/),
or
[webpack plugin](https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/)
tool may be all you need to start using Workbox.

In particular, the Workbox command line interface makes it easy to get up and
running, featuring a `wizard` mode that will check your local development
environment and suggest a reasonable default configuration that you could use
moving forward:

```bash
workbox wizard
? What is the root of your web app (i.e. which directory do you deploy)? src/
? Which file types would you like to precache? css, js, html
? Where would you like your service worker file to be saved? build/sw.js
? Where would you like to save these configuration options? workbox-config.js
```

To build your service worker, run `workbox generateSW workbox-config.js`
as part of a build process. See the [`generateSW` documentation](https://goo.gl/fdTQBf) for details.
You can further customize your service worker by making changes to `workbox-config.js`.
See the [documentation of the options](https://goo.gl/gVo87N) for details.

### Use Workbox at runtime in an existing service worker

If you have an existing service worker and want to try out Workbox's runtime
libraries,
[import Workbox from its official CDN](https://developer.chrome.com/docs/workbox/modules/workbox-sw/#using-workbox-sw-via-cdn)
and start using it for runtime caching right away. This use
case means that you won't be able to take advantage of precaching (which
requires build-time integration), but it's great for prototyping and trying out
different caching strategies on the fly.

```js
// Replace 3.6.3 with the current version number of Workbox.
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js');

workbox.routing.registerRoute(
  new RegExp('\.png$'),
  workbox.strategies.cacheFirst({
    cacheName: 'images-cache',
  })
);
```


# Workers overview

This overview explains how web workers and service workers can improve the performance of your website, and when to use a web worker versus a service worker. Check out the rest of [this series](#next-steps) for
specific patterns of window and service worker communication.

## How workers can improve your website {: #how-workers-can-improve-your-website }

The browser uses a single thread (the [main
thread](https://developer.mozilla.org/docs/Glossary/Main_thread)) to run all the JavaScript in
a web page, as well as to perform tasks like rendering the page and performing garbage collection.
Running excessive JavaScript code can block the main thread, delaying the browser from performing
these tasks and leading to a poor user experience.

In iOS/Android application development, a common pattern to ensure that the app's main thread
remains free to respond to user events is to offload operations to additional threads. In fact, in
the latest versions of Android, blocking the main thread for too long [leads to an app
crash](https://www.youtube.com/watch?v=eHjHlujp3Tg&feature=youtu.be&t=806).

On the web, JavaScript was designed around the concept of a single thread, and lacks capabilities
needed to implement a multithreading model like the one apps have, like shared memory.

Despite these limitations, a similar pattern can be achieved in the web by using
workers to run scripts in background
threads, allowing them to perform tasks without interfering with the main thread. Workers are an
entire JavaScript scope running on a separate thread, without any shared memory.

In this post you'll learn about two different types of workers (web workers and service workers), their similarities and differences, and the most common patterns for using them in production websites.

<figure>
  

*Diagram showing two links between the Window object and a web worker and service worker.*

</figure>

## Web workers and service workers {: #web-workers-and-service-workers }

### Similarities {: #similarities }

[Web workers](https://developer.mozilla.org/docs/Web/API/Web_Workers_API/Using_web_workers)
and [service
workers](https://developer.mozilla.org/docs/Web/API/Service_Worker_API/Using_Service_Workers)
are two types of workers available to websites. They have some things in common:

- Both run in a secondary thread, allowing JavaScript code to execute without blocking the main
  thread and the user interface.
- They don't have access to the [`Window`](https://developer.mozilla.org/docs/Web/API/Window)
  and [`Document`](https://developer.mozilla.org/docs/Web/API/Document) objects, so they can't
  interact with the DOM directly, and they have limited access to browser APIs.

### Differences {: #differences }

One might think that most things that can be delegated to a web worker can be done in a service
worker and vice versa, but there are important differences between them:

- Unlike web workers, service workers allow you to intercept network requests (via the
  [`fetch`](https://developer.mozilla.org/docs/Web/API/FetchEvent) event) and to listen for Push
  API events in the background (via the
  [`push`](https://developer.mozilla.org/docs/Web/API/PushEvent) event).
- A page can spawn multiple web workers, but a single service worker controls all the active tabs
  under the
  [scope](https://developer.mozilla.org/docs/Web/API/ServiceWorkerRegistration/scope) it was
  registered with.
- The lifespan of the web worker is tightly coupled to the tab it belongs to, while the [service
  worker's
  lifecycle](/service-worker-lifecycle/) is
  independent of it. For that reason, closing the tab where a web worker is running will terminate
  it, while a service worker can continue running in the background, even when the site doesn't have
  any active tabs open.

 For relatively short bits of work like sending a message, the browser won't likely
terminate a service worker when there are no active tabs, but if the task takes too long the browser
will terminate the service worker, otherwise it's a risk to the user's privacy and battery. APIs
like [Background Fetch](https://developer.chrome.com/blog/background-fetch/), that
can let you avoid the service worker's termination. 

## Use cases {: #use-cases }

The differences between both types of workers suggest in which situations one might want to use one
or the other:

**Use cases for web workers** are more commonly related to offloading work (like [heavy
computations](https://www.youtube.com/watch?v=mDdgfyRB5kg&feature=youtu.be&t=875)) to a secondary
thread, to avoid blocking the UI.

<figure>
  

*Diagram showing a link from the Window object to a web worker.*

</figure>

- **Example:** the team that built the videogame [PROXX](https://proxx.app/) wanted to leave the
  main thread as free as possible to take care of user input and animations. To achieve that, they
  [used web workers](/proxx-announce/#web-workers) to run the game logic and state
  maintenance on a separate thread.

<figure>
  

*A screenshot of the videogame PROXX.*

</figure>

**Service workers tasks** are generally more related to acting as a network proxy, handling
background tasks, and things like caching and offline.

<figure>
  

*A screenshot of the videogame PROXX.*

</figure>

**Example:** In a [podcast PWA](https://bgfetch-http203.glitch.me/), one might want to allow users
to download complete episodes to listen to them while offline. A service worker, and, in particular,
the [Background Fetch API](https://developer.chrome.com/blog/background-fetch/) can
be used to that end. That way, if the user closes the tab while the episode is downloading, the task
doesn't have to be interrupted.

<figure>
  

*A screenshot of a Podcast PWA.*

    <figcaption>The UI is updated to indicate the progress of a download (left). Thanks to service workers, the operation can continue running when all tabs have been closed (right).</figcaption>
</figure>

## Tools and libraries {: #tools-and-libraries }

Window and worker communication can be implemented by using different lower level APIs. Fortunately,
there are libraries that abstract this process, taking care of the most common use cases. In this
section, we'll cover two of them that take care of window to web workers and service workers
respectively: [Comlink](https://github.com/GoogleChromeLabs/comlink) and
[Workbox](https://developer.chrome.com/docs/workbox/).

<figure>
  

*A screenshot of the videogame PROXX.*

</figure>

### Comlink {: #comlink }

[Comlink](https://github.com/GoogleChromeLabs/comlink) is a small (1.6k)
[RPC](https://en.wikipedia.org/wiki/Remote_procedure_call) library that takes care of many
underlying details when building websites that use Web Workers. It has been used in websites
like [PROXX](https://proxx.app/) and [Squoosh](https://squoosh.app/). A summary of its motivations
and code samples can be found [here](https://surma.dev/things/when-workers/).

### Workbox {: #workbox }

[Workbox](https://developer.chrome.com/docs/workbox/) is a popular library to build websites
that use service workers. It packages a set of best practices around things like caching, offline,
background synchronization, etc. The [`workbox-window`](https://developer.chrome.com/docs/workbox/modules/workbox-window/) module provides a
convenient way to exchange messages between the service worker and the page.

## Next steps {: #next-steps }

The rest of this series focuses on patterns for window and service worker communication:

- [Imperative caching guide](/imperative-caching-guide): Calling a service worker from the page to
  cache resources in advance (e.g. in prefetching scenarios).
- [Broadcast updates](/broadcast-updates-guide/): Calling the page from the service worker to inform
  about important updates (e.g. a new version of the website is available).
- [Two-way communication](/two-way-communication-guide/): Delegating a task to a service worker
  (e.g. a heavy download), and keeping the page informed on the progress.

For patterns of window and web worker communication check out: [Use web workers to run JavaScript
off the browser's main thread](/off-main-thread/).