Service worker registration and scope
In one line: navigator.serviceWorker.register(scriptURL, { scope }) installs a service worker that intercepts fetch events for every URL whose path starts with the scope. The scope defaults to the directory containing the worker script and can only be narrowed, not widened beyond that, unless the server sends a Service-Worker-Allowed header.
How registration works
Section titled “How registration works”Calling register() is idempotent — the browser ignores the call if the identical script is already registered for that scope. The returned promise resolves to a ServiceWorkerRegistration object regardless of whether a new install was triggered.
if ('serviceWorker' in navigator) { navigator.serviceWorker .register('/sw.js', { scope: '/' }) .then((registration) => { console.log('Registered. Scope:', registration.scope); }) .catch((err) => { console.error('Registration failed:', err); });}Registration should happen after the page’s main content loads, not at the top of <head>, to avoid competing with critical resources.
What scope controls
Section titled “What scope controls”The scope is a URL path prefix. A service worker registered with scope /app/ intercepts:
/app//app/dashboard/app/settings/profile
But not:
/(root)/blog//app-launcher/(different prefix)
The scope does not restrict what URLs the worker can request via fetch() — it only limits which page navigations and subresource requests the worker is offered to handle.
Default scope
Section titled “Default scope”If no scope option is supplied, the scope defaults to the directory containing the worker script:
- Worker at
/sw.js→ default scope/ - Worker at
/app/sw.js→ default scope/app/
Placing the worker at the root (/sw.js) gives the widest default scope.
Widening scope with Service-Worker-Allowed
Section titled “Widening scope with Service-Worker-Allowed”A worker at /app/sw.js cannot normally claim / — its scope is bounded by its own path. To grant a wider scope, the server must include the Service-Worker-Allowed response header when the browser fetches the worker script:
Service-Worker-Allowed: /Without this header, registering /app/sw.js with { scope: '/' } throws a DOMException.
Multiple registrations
Section titled “Multiple registrations”A single origin can have more than one registration. This allows different workers for different sub-apps:
// Worker A covers the marketing sitenavigator.serviceWorker.register('/sw-marketing.js', { scope: '/marketing/' });
// Worker B covers the app shellnavigator.serviceWorker.register('/sw-app.js', { scope: '/app/' });Scopes may overlap — the browser matches the most specific (longest) scope when determining which worker handles a request. Keeping scopes non-overlapping is an operational best practice, not a registration constraint.
updateViaCache
Section titled “updateViaCache”By default the browser applies its own HTTP cache when fetching the worker script, which can delay updates if the script is aggressively cached. Set updateViaCache: 'none' to always bypass HTTP cache for the worker script itself:
navigator.serviceWorker.register('/sw.js', { scope: '/', updateViaCache: 'none',});The W3C Service Workers specification defines updateViaCache as controlling whether the HTTP cache is consulted for the worker script ('all'), its imported scripts ('imports', the default), or neither ('none').
Practical checklist
Section titled “Practical checklist”- Register after
DOMContentLoadedor in aloadevent to avoid resource contention. - Place the worker script as high in the path hierarchy as the scope you need (
/sw.jsfor/,/app/sw.jsfor/app/). - Use
updateViaCache: 'none'on the registration so the browser always checks for a new worker version. - Log
registration.scopein development to confirm the worker covers the expected paths. - For multi-app sites, define non-overlapping scopes and deploy separate workers per scope.
Cross-references
Section titled “Cross-references”- Service worker lifecycle — what happens after registration (install, activate, waiting)
- The update flow and skipWaiting — how new versions replace old ones
- Manifest scope — the related concept in the Web App Manifest