It's a wrap for Chrome Dev Summit 2020! Watch all the sessions at goo.gle/cds20-sessions now!

Workbox Recipes

What is Workbox Recipes?

A number of common patters, especially around routing and caching, are common enough that they can be standardized into reusable recipes. workbox-recipes makes these available in an easy-to-consume package, allowing you to get up-and-running with a highly functional service worker quickly.

Recipes

Each recipe combines a number of Workbox modules together, bundling them into commonly used patterns. The recipes below will show the recipe you use when using this module, and the equivalent pattern it's using under the hood, should you want to write it yourself.

Offline fallback

The offline fallback recipe allows your service worker to serve a web page, image, or font if there's a routing error for any of the three, for instance if a user is offline and there isn't a cache hit. In version 6.1.0 of Workbox Recipes, the requirement to cache these items using precaching was removed; for backwards compatibility, it will look for items in the precache first before trying its own cache.

This recipe, by default, assumes the fallback page is offline.html and that there isn't an image or font fallback. See the offline fallback options for a list of all configuration options.

Recipe

import { offlineFallback } from 'workbox-recipes';

offlineFallback();

Pattern

import { setCatchHandler } from 'workbox-routing';

const pageFallback = 'offline.html';
const imageFallback = false;
const fontFallback = false;

self.addEventListener('install', event => {
  const files = [pageFallback];
  if (imageFallback) {
    files.push(imageFallback);
  }
  if (fontFallback) {
    files.push(fontFallback);
  }

  event.waitUntil(self.caches.open('workbox-offline-fallbacks').then(cache => cache.addAll(files)));
});

const handler = async (options) => {
  const dest = options.request.destination;
  const cache = await self.caches.open('workbox-offline-fallbacks');

  if (dest === 'document') {
    return (await cache.match(pageFallback)) || Response.error();
  }

  if (dest === 'image' && imageFallback !== false) {
    return (await cache.match(imageFallback)) || Response.error();
  }

  if (dest === 'font' && fontFallback !== false) {
    return (await cache.match(fontFallback)) || Response.error();
  }

  return Response.error();
};

setCatchHandler(handler);

Warm strategy cache

The warm strategy cache recipe allows you to load provided URLs into your cache during the service worker's install phase, caching them with the options of the provided strategy. This can be used as an alternative to precaching if you know the specific URLs you'd like to cache, want to warm the cache of a route, or similar places where you'd like cache URLs during installation.

See the warm strategy cache options for a list of all configuration options.

Recipe

import { warmStrategyCache } from 'workbox-recipes';
import { CacheFirst } from 'workbox-strategies';

// This can be any strategy, CacheFirst used as an example.
const strategy = new CacheFirst();
const urls = [
  '/offline.html',
];

warmStrategyCache({urls, strategy});

Pattern

import { CacheFirst } from 'workbox-strategies';
// This can be any strategy, CacheFirst used as an example.
const strategy = new CacheFirst();
const urls = [
  '/offline.html',
];

self.addEventListener('install', event => {
  // handleAll returns two promises, the second resolves after all items have been added to cache.
  const done = urls.map(path => strategy.handleAll({
    event,
    request: new Request(path),
  })[1]);

  event.waitUntil(Promise.all(done));
});

Page cache

The page cache recipe allows your service worker to respond to a request for an HTML page (through URL navigation) with a network first caching strategy, optimized to, ideally, allow for the cache fallback to arrive quick enough for for a largest contentful paint score of less than 4.0 seconds.

This recipe, by default, assumes the network timeout should be 3 seconds and supports cache warming through the warmCache option. See the page cache options for a list of all configuration options.

Recipe

import { pageCache } from 'workbox-recipes';

pageCache();

Pattern

import { registerRoute } from 'workbox-routing';
import { NetworkFirst } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

const cacheName = 'pages';
const matchCallback = ({ request }) => request.mode === 'navigate';
const networkTimeoutSeconds = 3;

registerRoute(
  matchCallback,
  new NetworkFirst({
    networkTimeoutSeconds,
    cacheName,
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      }),
    ],
  }),
);

Static resources cache

The static resources cache recipe allows your service worker to respond to a request for static resources, specifically CSS, JavaScript, and Web Worker requests, with a stale-while-revalidate caching strategy so those assets can be quickly served from the cache and be updated in the background

This recipe supports cache warming through the warmCache option. See the static resources cache options for a list of all configuration options.

Recipe

import { staticResourceCache } from 'workbox-recipes';

staticResourceCache();

Pattern

import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

const cacheName = 'static-resources';
const matchCallback = ({ request }) =>
  // CSS
  request.destination === 'style' ||
  // JavaScript
  request.destination === 'script' ||
  // Web Workers
  request.destination === 'worker';

registerRoute(
  matchCallback,
  new StaleWhileRevalidate({
    cacheName,
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      }),
    ],
  }),
);

Image cache

The image cache recipe allows your service worker to respond to a request for images with a cache-first caching strategy so that once they're available in cache a user doesn't need to make another request for them.

This recipe, by default, caches a maximum of 60 images, each for 30 days and supports cache warming through the warmCache option. See the image cache options for a list of all configuration options.

Recipe

import { imageCache } from 'workbox-recipes';

imageCache();

Pattern

import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { ExpirationPlugin } from 'workbox-expiration';

const cacheName = 'images';
const matchCallback = ({ request }) => request.destination === 'image';
const maxAgeSeconds = 30 * 24 * 60 * 60;
const maxEntries = 60;

registerRoute(
  matchCallback,
  new CacheFirst({
    cacheName,
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      }),
      new ExpirationPlugin({
        maxEntries,
        maxAgeSeconds,
      }),
    ],
  }),
);

Google Fonts cache

The Google Fonts recipe caches the two parts of a Google Fonts request:

  • The stylesheet with the @font-face definitions, which link to the font files.
  • The static, revisioned font files.

Because the stylesheet can change frequently, a stale-while-revalidate caching strategy is used. The font files themselves, on the other hand, do not change and can leverage a cache first strategy.

This recipe, by default, caches a maximum of 30 font files, each for one year. See the Google Fonts cache options for a list of all configuration options.

Recipe

import { googleFontsCache } from 'workbox-recipes';

googleFontsCache();

Pattern

import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
import { CacheFirst } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { ExpirationPlugin } from 'workbox-expiration';

const sheetCacheName = 'google-fonts-stylesheets';
const fontCacheName = 'google-fonts-webfonts';
const maxAgeSeconds = 60 * 60 * 24 * 365;
const maxEntries = 30;

registerRoute(
  ({ url }) => url.origin === 'https://fonts.googleapis.com',
  new StaleWhileRevalidate({
    cacheName: sheetCacheName,
  }),
);

// Cache the underlying font files with a cache-first strategy for 1 year.
registerRoute(
  ({ url }) => url.origin === 'https://fonts.gstatic.com',
  new CacheFirst({
    cacheName: fontCacheName,
    plugins: [
      new CacheableResponsePlugin({
        statuses: [0, 200],
      }),
      new ExpirationPlugin({
        maxAgeSeconds,
        maxEntries,
      }),
    ],
  }),
);

Quick usage

Combining all of the recipes together will yield a service worker that responds to page requests with a network first caching strategy, respond to CSS, JavaScript, and Web Worker requests with a stale-while-revalidate strategy, respond to image requests with a cache first strategy, properly cache Google Fonts, and provide an offline fallback for page requests. This can all be done with the following:

import {
  pageCache,
  imageCache,
  staticResourceCache,
  googleFontsCache,
  offlineFallback,
} from 'workbox-recipes';
import { precacheAndRoute } from 'workbox-precaching';

// Include offline.html in the manifest
precacheAndRoute(self.__WB_MANIFEST);

pageCache();

googleFontsCache();

staticResourceCache();

imageCache();

offlineFallback();

Feedback

Was this page helpful?
Yes
What was the best thing about this page?
It helped me complete my goal(s)
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It had the information I needed
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It had accurate information
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It was easy to read
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
Something else
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
No
What was the worst thing about this page?
It didn't help me complete my goal(s)
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It was missing information I needed
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It had inaccurate information
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
It was hard to read
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.
Something else
Thank you for the feedback. If you have specific ideas on how to improve this page, please create an issue.