# Update strategies

> Choose how a new service worker reaches users: the waiting default, immediate takeover with skipWaiting(), a user-prompted reload, and periodic update checks.

At the end of this guide a new version of your service worker reaches users the way you
chose, with the page code and worker code that implement that choice: the browser's
waiting default, an immediate takeover, or a reload the user triggers from an "update
available" notice. The mechanism (`skipWaiting()`, `clients.claim()`, the waiting state)
is described in
[The service worker update flow and skipWaiting](/reference/service-worker/update-skipwaiting/);
this guide is about picking one and wiring it up.

You need a registered worker whose script changes between deploys. The browser treats the
worker as updated when the fetched script is byte-different from the one it holds, and it
checks on navigation to an in-scope page, on `push` and `sync` events (unless a check ran
within the previous 24 hours), and on `register()` when the script URL changed. Most
browsers ignore HTTP caching headers for that check.

## Keep the waiting default

Without `skipWaiting()`, a newly installed worker waits until the old worker controls zero
clients. The default buys consistency: a page keeps the worker version it loaded under,
so the cache it was built against is the cache that answers. The cost is that a reload is
not enough; during a refresh the old and new page overlap, so the old worker is still
controlling a client, and the update applies only after the user closes or navigates away
from every tab. Pick this when users close tabs within a day and a stale version for that
long is acceptable.

```js
// sw.js: nothing to add. The install handler only precaches.
self.addEventListener('install', (event) => {
  event.waitUntil(caches.open('shell-v4').then((cache) => cache.addAll(['/', '/app.js'])));
});
```

## Activate immediately with skipWaiting()

`self.skipWaiting()` in `install` makes the new worker activate as soon as installation
finishes, even while the old worker's pages are open. Pair it with `clients.claim()` in
`activate` when the first install should control pages that loaded without a worker. The
cost: the new worker now answers fetches for HTML that an older version produced, so a
cache or schema change that is not backwards compatible breaks those open pages. Use it
for non-breaking fixes that should land at once.

```js
// sw.js
self.addEventListener('install', (event) => {
  self.skipWaiting(); // the promise it returns can be ignored
  event.waitUntil(caches.open('shell-v4').then((cache) => cache.addAll(['/', '/app.js'])));
});

self.addEventListener('activate', (event) => {
  event.waitUntil(self.clients.claim());
});
```

## Let the user choose when to reload

The middle path installs the new worker, lets it wait, tells the user, and calls
`skipWaiting()` only when they accept, followed by one controlled reload. It avoids both
the silent inconsistency of unconditional `skipWaiting()` and the "update does not apply"
cost of the default. The worker listens for a message; the page watches the registration
for a waiting worker and reloads once on `controllerchange`.

```js
// sw.js
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting();
});
```

```js
// page
const registration = await navigator.serviceWorker.register('/sw.js');

function offerUpdate(worker) {
  const banner = document.querySelector('#update-banner');
  banner.hidden = false;
  banner.querySelector('button').onclick = () => worker.postMessage({ type: 'SKIP_WAITING' });
}

if (registration.waiting) offerUpdate(registration.waiting);
registration.addEventListener('updatefound', () => {
  const installing = registration.installing;
  installing.addEventListener('statechange', () => {
    if (installing.state === 'installed' && navigator.serviceWorker.controller) offerUpdate(installing);
  });
});

let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
  if (refreshing) return;
  refreshing = true;
  location.reload();
});
```

The `navigator.serviceWorker.controller` check skips the banner on the very first install,
when there is no old worker to replace.

:::observed
In Chrome DevTools, **Application** > **Service workers** shows the new worker as `waiting
to activate` with a **skipWaiting** link next to it while the banner above is visible.
With **Update on reload** checked instead, DevTools refetches the script on every
navigation, installs it even when byte-identical, skips waiting, and navigates, so the
banner does not appear; uncheck it before testing this flow.
:::

## Check for updates in long-lived tabs

A tab left open for days does not navigate, so it does not trigger the browser's check.
`registration.update()` fetches the script and installs it if it differs, bypassing the
HTTP cache when the previous fetch is older than 24 hours. An hourly call keeps a kiosk or
dashboard within an hour of the latest deploy at the cost of one small request per hour.

```js
navigator.serviceWorker.register('/sw.js').then((registration) => {
  setInterval(() => registration.update(), 60 * 60 * 1000);
});
```

## Match the strategy to the risk

| Situation | Strategy | What it costs |
|---|---|---|
| Users close or navigate away from tabs daily | Waiting default | Update lands only after the last tab closes |
| Tabs stay open for hours or days | Default plus hourly `registration.update()` | One extra request per hour per tab |
| Release changes cache layout or IndexedDB schema | Default or user-prompted reload | A banner to design and a reload the user must accept |
| Non-breaking fix that should apply at once | `skipWaiting()` in `install` | New worker answers pages built by the old version |

Deploy a byte-changed `sw.js`, open the app in two tabs, and watch the chosen behaviour in
the Service workers pane: the default shows `waiting to activate` until both tabs close;
`skipWaiting()` shows the new worker `activated and is running` on the next reload; the
prompted flow shows the banner, then the reload.

## See also

- [The service worker update flow and skipWaiting](/reference/service-worker/update-skipwaiting/)
- [Service worker lifecycle: install to update](/reference/service-worker/lifecycle/)
- [Offline strategies](/guides/offline/)
- [The service worker lifecycle](https://web.dev/articles/service-worker-lifecycle) (web.dev)
- [ServiceWorkerGlobalScope: skipWaiting() method](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting) (developer.mozilla.org)
- [ServiceWorkerRegistration: update() method](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/update) (developer.mozilla.org)

← Back to the [Guides](/guides/) overview.