Adding a Service Worker
A service worker is the engine that powers an installable, offline-capable Angular app. Rather than wiring up the low-level Service Worker API by hand, Angular ships @angular/service-worker together with a schematic that configures everything for you in a single command. This page walks through running ng add @angular/pwa, understanding the generated ngsw-config.json, and how the worker gets registered and served.
Installing with ng add
The fastest, least error-prone way to add a service worker is the official PWA schematic. From the root of an existing Angular project, run:
ng add @angular/pwa
The schematic performs several edits at once: it installs @angular/service-worker, generates an ngsw-config.json at the project root, adds the manifest and icon assets, registers the worker in your application config, and flips on "serviceWorker": true in angular.json.
Output:
Installing packages for tooling via npm.
Installed packages for tooling via npm.
CREATE ngsw-config.json (664 bytes)
CREATE public/manifest.webmanifest (1366 bytes)
CREATE public/icons/icon-512x512.png (...)
UPDATE angular.json (...)
UPDATE src/index.html (...)
UPDATE src/app/app.config.ts (...)
The service worker only runs in production-style builds.
ng servedeliberately does not register it, so you must build and serve the output (covered below) to see it in action.
Registering the worker
For a standalone application, the schematic adds provideServiceWorker to your app.config.ts. It registers ngsw-worker.js and waits until the app is stable (or 30 seconds elapse) before activating, so registration never competes with your initial render.
// src/app/app.config.ts
import { ApplicationConfig, isDevMode } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideServiceWorker } from '@angular/service-worker';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideServiceWorker('ngsw-worker.js', {
enabled: !isDevMode(),
registrationStrategy: 'registerWhenStable:30000',
}),
],
};
The registrationStrategy controls when registration happens. The available strategies are:
| Strategy | Behaviour |
|---|---|
registerWhenStable:<ms> | Wait for app stability, falling back to the timeout in ms |
registerImmediately | Register as soon as the app bootstraps |
registerWithDelay:<ms> | Register after a fixed delay |
| A custom factory | A function returning an Observable that fires when you want |
Understanding ngsw-config.json
ngsw-config.json is the source of truth for what the service worker caches and how. It is read at build time and compiled into ngsw.json (the runtime manifest). A freshly generated file looks like this:
// ngsw-config.json
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/media/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
}
A few keys are worth knowing up front:
index— the fallback document served for navigation requests, which is what makes deep links work offline in a single-page app.assetGroups— bundles of static files baked into the build. Theappgroup usesprefetchso the application shell is cached on install; theassetsgroup useslazyso large media is only cached on first request.installMode—prefetchcaches everything immediately at install;lazycaches a resource the first time it is requested.updateMode— how cached resources refresh when a new version ships;prefetchre-downloads in the background,lazywaits for the next request.
You can also add a dataGroups array to cache API responses, and a navigationUrls array to control which navigation requests fall back to index.html. Those are covered in the caching page linked below.
Building and serving
Because the worker is disabled in dev mode, you need a real build plus a static server to exercise it.
ng build
npx http-server -p 8080 -c-1 dist/my-app/browser
Open http://localhost:8080, then check the browser DevTools under Application → Service Workers. You should see ngsw-worker.js activated and running.
Output:
Starting up http-server, serving dist/my-app/browser
Available on:
http://127.0.0.1:8080
Hit CTRL-C to stop the server
To confirm offline support, tick Offline in the DevTools Network panel and reload — the app shell still loads from the cache rather than the network.
Best practices
- Always build with
ng build(production configuration) and serve the static output; never expect the worker to run underng serve. - Keep
prefetchfor the app shell so the first offline visit works, and reservelazyfor heavy or optional media to avoid bloating the install. - Serve over HTTPS in production — service workers are blocked on insecure origins, with
localhostas the only exception. - Disable HTTP caching of
ngsw.jsonandngsw-worker.json your server so clients reliably pick up new versions. - Edit
ngsw-config.jsonrather than hand-writing worker logic; let Angular compile the runtime manifest for you. - Use
provideServiceWorkerwithenabled: !isDevMode()so the worker is environment-aware without manual flags.