service-worker
Service Worker
Table of Contents
- Constraints
- Lifecycle
- Registration
- Install / Activate / Fetch Events
- Common Pitfalls
- Next.js Integration
- Reference files
Constraints
- HTTPS required (localhost exempt for dev)
- No DOM access — runs on separate thread
- Fully async — no synchronous XHR, no localStorage
- No dynamic
import()— only staticimportstatements - Scope defaults to the directory containing the SW file
selfrefers toServiceWorkerGlobalScope
<quick_reference>
Lifecycle
register() → Download → Install → [Wait] → Activate → Fetch control
- Register from main thread via
navigator.serviceWorker.register() - Install event fires once — use to pre-cache static assets
- Wait — new SW waits until all tabs using old SW are closed (skip with
self.skipWaiting()) - Activate event fires — use to clean up old caches
- Fetch events start flowing — SW controls page network requests
A document must reload to be controlled (or call clients.claim() during activate).
Updating a Service Worker
- Browser byte-compares the SW file on each navigation (or every 24h)
- New version installs in background while old version still serves
- Increment the cache name (e.g.,
v1→v2) in the new version - Delete old caches in the
activatehandler - Call
self.skipWaiting()ininstallto activate immediately - Call
self.clients.claim()inactivateto take control of open pages
DevTools
- Chrome:
chrome://inspect/#service-workersor Application > Service Workers - Firefox:
about:debugging#/runtime/this-firefoxor Application > Service Workers - Edge:
edge://inspect/#service-workersor Application > Service Workers
Unregister, update, and inspect caches from the Application panel. Use "Update on reload" checkbox during development.
</quick_reference>
Registration
// main.js — register from the page
if ("serviceWorker" in navigator) {
const reg = await navigator.serviceWorker.register("/sw.js", { scope: "/" });
// reg.installing | reg.waiting | reg.active
}
Scope rules:
- SW at
/sw.jscan control/and all subpaths - SW at
/app/sw.jscan only control/app/by default - Broaden scope with
Service-Worker-Allowedresponse header
Install Event — Pre-cache Assets
// sw.js
const CACHE_NAME = "v1";
const PRECACHE_URLS = ["/", "/index.html", "/style.css", "/app.js"];
self.addEventListener("install", (event) => {
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)));
});
waitUntil(promise) — keeps install phase alive until the promise settles. If rejected, installation fails and the SW won't activate.
Activate Event — Clean Up Old Caches
self.addEventListener("activate", (event) => {
event.waitUntil(
caches
.keys()
.then((keys) =>
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))),
),
);
});
Fetch Event — Intercept Requests
self.addEventListener("fetch", (event) => {
event.respondWith(caches.match(event.request).then((cached) => cached || fetch(event.request)));
});
respondWith(promise) — must be called synchronously (within the event handler, not in a microtask). The promise resolves to a Response.
For caching strategy patterns (cache-first, network-first, stale-while-revalidate), see references/caching-strategies.md.
Navigation Preload
Avoid the startup delay when a SW boots to handle a navigation:
self.addEventListener("activate", (event) => {
event.waitUntil(self.registration?.navigationPreload.enable());
});
self.addEventListener("fetch", (event) => {
event.respondWith(
(async () => {
const cached = await caches.match(event.request);
if (cached) return cached;
const preloaded = await event.preloadResponse;
if (preloaded) return preloaded;
return fetch(event.request);
})(),
);
});
Communicating with Pages
// Page → SW
navigator.serviceWorker.controller.postMessage({ type: "SKIP_WAITING" });
// SW → Page (via Clients API)
const clients = await self.clients.matchAll({ type: "window" });
clients.forEach((client) => client.postMessage({ type: "UPDATED" }));
// SW listens
self.addEventListener("message", (event) => {
if (event.data?.type === "SKIP_WAITING") self.skipWaiting();
});
Next.js Integration
In Next.js, place the service worker file in public/sw.js. public/sw.js is intentionally plain JS (not processed by Next.js build pipeline). Register it from a client component:
"use client";
import { useEffect } from "react";
export function ServiceWorkerRegistrar() {
useEffect(() => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js");
}
}, []);
return null;
}
Add to root layout. Next.js serves public/ files at the root, so /sw.js scope covers /.
Common Pitfalls
- Response cloning —
response.clone()before both caching and returning, since body streams can only be read once - Opaque responses — cross-origin fetches without CORS return opaque responses (status 0).
cache.add()will refuse them. Usecache.put()but you can't inspect the response - waitUntil timing — call
event.waitUntil()synchronously within the event handler, not inside an async callback - Scope ceiling — a SW cannot control URLs above its own directory unless
Service-Worker-Allowedheader is set - No state persistence — the SW may terminate at any time when idle. Don't store state in global variables — use Cache API or IndexedDB
Reference files
- Caching strategies (cache-first, network-first, stale-while-revalidate): references/caching-strategies.md
- Push notifications & background sync (push subscription, push events, background sync): references/push-and-sync.md
- API quick reference (
Cache,CacheStorage,FetchEvent,Clients,ServiceWorkerRegistration,ServiceWorkerGlobalScope): references/api-reference.md
More from jgamaraalv/ts-dev-kit
bullmq
BullMQ queue system reference for Redis-backed job queues, workers, flows, and schedulers. Use when: (1) creating queues and workers with BullMQ, (2) adding jobs (delayed, prioritized, repeatable, deduplicated), (3) setting up FlowProducer parent-child job hierarchies, (4) configuring retry strategies, rate limiting, or concurrency, (5) implementing job schedulers with cron/interval patterns, (6) preparing BullMQ for production (graceful shutdown, Redis config, monitoring), or (7) debugging stalled jobs or connection issues
46owasp-security-review
Review code and architectures against the OWASP Top 10:2025 — the ten most critical web application security risks. Use when: (1) reviewing code for security vulnerabilities, (2) auditing a feature or codebase against OWASP categories, (3) providing remediation guidance for identified vulnerabilities, (4) writing new code and needing secure coding patterns. Triggers: 'review for security', 'OWASP audit', 'check for vulnerabilities','security checklist', 'is this code secure', 'security review', 'fix vulnerability'.
42ioredis
ioredis v5 reference for Node.js Redis client — connection setup, RedisOptions, pipelines, transactions, Pub/Sub, Lua scripting, Cluster, and Sentinel. Use when: (1) creating or configuring Redis connections (standalone, cluster, sentinel), (2) writing Redis commands with ioredis (get/set, pipelines, multi/exec), (3) setting up Pub/Sub or Streams, (4) configuring retryStrategy, TLS, or auto-pipelining, (5) working with Redis Cluster options (scaleReads, NAT mapping), or (6) debugging ioredis connection issues. Important: use named import `import { Redis } from 'ioredis'` for correct TypeScript types with NodeNext.
35nextjs-best-practices
Next.js App Router best practices — file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling. Use when writing, reviewing, or debugging Next.js App Router code.
29ui-ux-guidelines
Review UI code for Web Interface Guidelines compliance. Use when asked to review UI, check accessibility, audit design, review UX, or check against best practices.
26composition-patterns
React composition patterns that scale. Use when refactoring components with boolean prop proliferation, building flexible component libraries, or designing reusable APIs. Triggers on tasks involving compound components, render props, context providers, or component architecture. Includes React 19 API changes.
23