Offline & Installable

PWA & Service Workers

A Progressive Web App installs like a native app, works offline and loads instantly on repeat visits. Service workers and a web manifest make it possible.

advanced14 min readUpdated Sep 15, 2026
sw.js
js
// sw.js
const CACHE = "app-v1";
const ASSETS = ["/", "/app.js", "/styles.css"];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE).then((cache) => cache.addAll(ASSETS)),
  );
});

self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => cached || fetch(event.request)),
  );
});
Requires
HTTPS or localhost
Manifest
manifest.webmanifest
Worker
service worker
Caching
Cache Storage API
Install
Add to home screen
Key benefit
Offline and instant repeats

Why it matters

Why build a PWA

Works offline

A service worker serves cached assets and data, so the app keeps working when the network drops.

Instant repeat visits

Cached assets load immediately, turning a slow first visit into an instant second one.

Installable

With a manifest and a worker, browsers offer to install the app to the home screen or desktop.

The big picture

The three parts of a PWA

A manifest that describes the app, a service worker that controls the network, and a secure origin that makes both possible.

The manifest

Describe

Name, icons, colours and display mode tell the platform how to present the app.

The service worker

Intercept

A background script that sits between the page and the network, controlling requests.

The cache

Store

The Cache Storage API holds responses the worker can serve later.

PWA at a glance

The core of a PWA

Web manifest

Metadata that makes the app installable and controls its appearance.

Service worker

A script that intercepts fetch events and manages caches.

Cache Storage

A key-value store for request and response pairs.

Caching strategy

Cache-first, network-first, stale-while-revalidate and more.

Offline fallback

A page or state shown when nothing is cached and the network is down.

Background sync

Retry failed requests when connectivity returns.

A short history

From offline hack to installable web apps

  1. 2014

    Service workers proposed

    A new worker type gives pages a programmable network layer.

    14
  2. 2015

    Progressive Web Apps

    The term is coined to describe installable, offline-capable web experiences.

    15
  3. 2018

    Broad browser support

    Service workers and install prompts reach all major browsers.

    18
  4. 2020

    Workbox maturity

    Google's Workbox library makes caching strategies easier to implement safely.

    20
  5. Today

    Mainstream

    Many major sites ship a service worker for offline support and speed.

    Today

The complete guide

PWA & Service Workers: Everything you need to know

What is a PWA?

A Progressive Web App is a website that behaves like an installed app: it works offline, loads instantly on repeat visits and can be added to the home screen or desktop. There is no single technology called a PWA — it is a set of capabilities you adopt incrementally.

Three ingredients make it work:

  1. HTTPS, so the browser trusts your origin enough to allow a service worker.
  2. A web manifest that describes the app’s name, icons and display mode.
  3. A service worker, a background script that intercepts network requests and manages caches.

The result is an experience that feels native while remaining a website you can update instantly.

The web manifest

The manifest is a JSON file linked from the HTML head. It tells the platform how to present the app when installed.

{
  "name": "hackweb Reader",
  "short_name": "Reader",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#0b0d0c",
  "theme_color": "#0b0d0c",
  "icons": [
    { "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" }
  ]
}
<!-- index.html -->
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#0b0d0c" />

display: "standalone" hides the browser chrome when installed, and the icons and colours control the splash screen and task switcher. A valid manifest plus a service worker is what makes the browser offer to install the app.

The service worker lifecycle

A service worker is a separate script with no DOM access. It has a distinct lifecycle: install, activate and fetch.

// register.js
if ("serviceWorker" in navigator) {
  window.addEventListener("load", () => {
    navigator.serviceWorker.register("/sw.js");
  });
}

During install you pre-cache the app shell. During activate you clean up old caches. During fetch you intercept requests and decide how to respond. A new worker waits until all pages using the old one are closed before activating, which is why updates sometimes need a second reload.

Caching strategies

The heart of a service worker is its caching strategy. Different resources want different treatment.

  • Cache-first — serve from cache, fall back to the network. Ideal for versioned, immutable assets like hashed bundles.
  • Network-first — try the network, fall back to cache. Good for HTML and data that must be fresh.
  • Stale-while-revalidate — serve the cached version immediately and update it in the background. Great for data where slightly stale is acceptable.
  • Cache-only and network-only — for specific edge cases.
// strategies.js
self.addEventListener("fetch", (event) => {
  const { request } = event;
  if (request.destination === "image") {
    event.respondWith(
      caches.match(request).then((cached) => cached || fetch(request)),
    );
  }
});

The request.destination property tells you what kind of resource is being fetched, which lets you apply a strategy per asset type.

Offline fallback

Even with caching, some requests will fail. Provide a graceful fallback.

// fallback.js
self.addEventListener("fetch", (event) => {
  if (event.request.mode === "navigate") {
    event.respondWith(
      fetch(event.request).catch(() => caches.match("/offline.html")),
    );
  }
});

An offline page, a cached shell or a clear “you are offline” state is far better than the browser’s default error. Combined with background sync, requests that failed offline can be retried automatically when connectivity returns.

Updates and versioning

Caching is powerful and easy to get wrong. The rule is simple: version your caches and delete old ones on activate. Otherwise users can be stuck on stale files indefinitely.

// activate.js
const CACHE = "app-v2";

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))),
    ),
  );
});

For anything beyond the basics, the Workbox library handles precaching, routing, expiration and cleanup with well-tested defaults, and integrates with most build tools.

Beyond offline

Service workers enable more than caching:

  • Background sync retries failed requests when the network returns.
  • Push notifications deliver messages even when the app is closed.
  • Periodic sync refreshes content in the background.
  • Share target lets your app receive shared content from the OS.

Each requires user permission and should be used sparingly, but they are what make a PWA feel like a first-class app.

Best practices

  • Serve over HTTPS and register the worker after load.
  • Version caches and delete old ones on activate.
  • Use cache-first for hashed assets and network-first for HTML.
  • Always provide an offline fallback.
  • Precache only the app shell, not the entire site.
  • Consider Workbox instead of hand-writing complex strategies.
  • Test offline mode and updates before shipping.

Common mistakes

  • Caching HTML forever and serving stale pages.
  • Forgetting to clean up old caches after a deploy.
  • Caching opaque cross-origin responses without understanding their limits.
  • Precaching everything and bloating the first install.
  • Assuming the new worker activates immediately.
  • Ignoring the storage quota and letting caches grow unbounded.

Where to go next

A service worker is the biggest single performance win for repeat visits and the foundation of offline support. Combine it with the techniques in the Web Performance guide, understand the HTTP requests it intercepts, and serve the app from a Node.js backend. Then add a manifest and a simple cache-first worker to an existing site and watch it work offline.

Choosing a caching strategy

Cache-first is fast and offline-friendly for versioned assets. Network-first suits content that must be fresh.

Cache-first
// hashed assets never change
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then(
      (cached) => cached || fetch(event.request),
    ),
  );
});
Network-first
// for HTML or fresh data:
// try the network, fall back
event.respondWith(
  fetch(event.request).catch(() =>
    caches.match(event.request),
  ),
);

Updating cached assets

Change the cache name on each deploy and delete old caches so users do not get stuck on stale files.

Prefer
const CACHE = "app-v2";

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(
        keys
          .filter((k) => k !== CACHE)
          .map((k) => caches.delete(k)),
      ),
    ),
  );
});
Avoid
// same cache name forever,
// old files never replaced

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning PWA & Service Workers?

Our interactive tutorial walks you through PWA & Service Workers step by step — with quizzes and real code you can run in the browser.