# Workbox

> What the Workbox 7 packages do, how precacheAndRoute() and __WB_MANIFEST build versioned caches, when GenerateSW or InjectManifest applies, and page updates.

Workbox is a set of JavaScript libraries, maintained by the Chrome team, that implement the routing, caching, precaching, and update-messaging code a service worker otherwise repeats by hand; the current major version is 7 (released 2023-04). It runs on the standard Service Worker, Cache, and Fetch APIs only, so it works in every browser with a service worker (Chrome 40, Firefox 44, Safari 11.1, Edge 17; BCD `api.ServiceWorker`), and its optional modules degrade where their underlying API is missing, such as `workbox-background-sync` in Firefox and Safari.

## How it works

Each `workbox-*` package maps to one service worker task; the worker script imports only what it uses, either from npm through a bundler or as ES modules from a CDN.

| Package | What it replaces in a hand-written worker |
|---|---|
| `workbox-routing` | The `if`/`else` chain in the `fetch` handler: `registerRoute(matcher, handler)` and `NavigationRoute` |
| `workbox-strategies` | The five strategy bodies: `CacheFirst`, `NetworkFirst`, `StaleWhileRevalidate`, `NetworkOnly`, `CacheOnly`, with `networkTimeoutSeconds` and plugin hooks |
| `workbox-precaching` | The `install`/`activate` cache fill and cleanup, keyed by content revision |
| `workbox-expiration` | Cache size and age limits (`maxEntries`, `maxAgeSeconds`) |
| `workbox-cacheable-response` | The `response.ok` check before `cache.put()` |
| `workbox-background-sync` | An IndexedDB queue replayed on the `sync` event; in browsers without Background Sync the queue is replayed whenever the worker next starts |
| `workbox-broadcast-update` | A `BroadcastChannel` message when a Stale-While-Revalidate refresh changes a cached response |
| `workbox-window` | Page-side registration, update detection, and `skipWaiting` messaging |

Precaching is the part that needs a build step. A build tool scans the output directory and emits an array of `{ url, revision }` entries into the worker in place of the `self.__WB_MANIFEST` token; `precacheAndRoute()` stores each file in a cache named `workbox-precache-v2-<scope>`, keyed by URL with the revision appended as a `__WB_REVISION__` query parameter, and installs a Cache First route for those URLs. On update, only entries whose revision changed are fetched; the `activate` handler deletes entries no longer in the manifest. Files whose name already contains a content hash get `revision: null` so the URL alone identifies the version.

Two build modes produce that manifest. `GenerateSW` (in `workbox-build`, `workbox-cli`, `workbox-webpack-plugin`, and `vite-plugin-pwa`) writes the complete worker from configuration and is sufficient when the worker needs nothing beyond precaching and runtime caching rules; `InjectManifest` takes a worker source file you write, replaces the `__WB_MANIFEST` token, and is required for any custom `fetch`, `push`, `sync`, or `message` handler. The trade-off is that `GenerateSW` regenerates the worker on every build so configuration is the only source of truth, while `InjectManifest` makes the worker ordinary code at the cost of maintaining the imports yourself.

On the page, `workbox-window`'s `Workbox` class wraps `register()` and raises `installed`, `waiting`, `controlling`, and `activated` events; `wb.messageSkipWaiting()` posts a `{ type: 'SKIP_WAITING' }` message; a `GenerateSW` worker includes the matching `message` listener, while an `InjectManifest` worker must add the four-line listener shown in the first example.

:::observed
A Workbox 7 development build (selected when the bundler sets `process.env.NODE_ENV` to anything but `production`, or when the `-dev` CDN files are loaded) logs to the Console with a colour-coded `workbox` badge: on every intercepted request it prints a collapsed group such as `workbox Router is responding to: /app.js` whose expanded rows name the strategy (`Using CacheFirst to respond to '/app.js'`) and whether the response came from the cache; on install it prints `workbox Precaching 42 files. 3 files are already cached.`. The production build prints nothing. Wording follows the Workbox "Troubleshooting and logging" page in the sources.
:::

## Examples

The first example is a complete `InjectManifest` worker with precaching and one runtime route; the second is the page side, including the branch for a browser without service workers.

### An InjectManifest worker with precaching and an image route

The `__WB_MANIFEST` token is replaced at build time; the `ExpirationPlugin` bounds the runtime cache, which precaching does not need because the manifest already bounds it.

```js
// src/sw.js: the swSrc file; the build tool fills __WB_MANIFEST
import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';

cleanupOutdatedCaches();                 // removes caches from older precache formats
precacheAndRoute(self.__WB_MANIFEST);    // [{ url: '/index.html', revision: 'a1b2' }, …]

self.addEventListener('message', (event) => {
  if (event.data?.type === 'SKIP_WAITING') self.skipWaiting(); // paired with workbox-window
});

registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new CacheableResponsePlugin({ statuses: [0, 200] }),
      new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60 }),
    ],
  })
);
```

Including `0` in `statuses` caches opaque cross-origin images, which Chromium pads to about 7 MB each in quota accounting; omit it when the images are same-origin or served with CORS so that only real `200` responses are stored.

### Registering with workbox-window and detecting support

`Workbox.register()` resolves to the registration or rejects with the same errors as `navigator.serviceWorker.register()`. When `serviceWorker` is absent the page keeps working online-only, so the import is placed behind the feature test to avoid loading the module for nothing.

```js
// page.js
if ('serviceWorker' in navigator) {
  const { Workbox } = await import('workbox-window');
  const wb = new Workbox('/sw.js');

  wb.addEventListener('waiting', () => {
    showBanner('A new version is ready.', () => {
      wb.addEventListener('controlling', () => window.location.reload());
      wb.messageSkipWaiting();
    });
  });

  wb.register();
} else {
  // No service worker: skip the update banner; the app runs from the network.
}
```

Reloading inside `controlling` rather than immediately after `messageSkipWaiting()` waits for the new worker to take over, so the reloaded page is served by the new precache; reloading sooner can load a mix of old HTML and new assets.

## See also

- [Workbox: precaching module](https://developer.chrome.com/docs/workbox/modules/workbox-precaching) (developer.chrome.com)
- [Workbox: the ways of Workbox](https://developer.chrome.com/docs/workbox/the-ways-of-workbox) (developer.chrome.com)
- [Caching strategies for service workers](/reference/service-worker/caching-strategies/)
- [skipWaiting() and the update flow](/reference/service-worker/update-skipwaiting/)
- [Precaching](/reference/performance/precaching/)
- [Offline fallback for service workers](/reference/service-worker/offline-fallback/)