From ts-dev-kit
Guides Service Worker API implementation: registration, lifecycle, caching strategies (cache-first, network-first), push notifications, background sync, and debugging sw.js files.
npx claudepluginhub jgamaraalv/ts-dev-kit --plugin ts-dev-kitThis skill uses the workspace's default tool permissions.
- [Constraints](#constraints)
Implements Service Worker caching for PWAs: lifecycle management (install/activate/fetch), strategies (cache-first/network-first/stale-while-revalidate), offline support, Workbox runtime caching, background sync, cache versioning.
Builds Progressive Web Apps with service workers for offline caching, web app manifests for installability, and strategies like Workbox for resilient web experiences.
Builds Progressive Web Apps with service workers for offline caching, web manifests for installability, install prompts, and push notifications. Use for adding native-like features or fixing service worker/manifest errors.
Share bugs, ideas, or general feedback.
import() — only static import statementsself refers to ServiceWorkerGlobalScope<quick_reference>
register() → Download → Install → [Wait] → Activate → Fetch control
navigator.serviceWorker.register()self.skipWaiting())A document must reload to be controlled (or call clients.claim() during activate).
v1 → v2) in the new versionactivate handlerself.skipWaiting() in install to activate immediatelyself.clients.claim() in activate to take control of open pageschrome://inspect/#service-workers or Application > Service Workersabout:debugging#/runtime/this-firefox or Application > Service Workersedge://inspect/#service-workers or Application > Service WorkersUnregister, update, and inspect caches from the Application panel. Use "Update on reload" checkbox during development.
</quick_reference>
// 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.js can control / and all subpaths/app/sw.js can only control /app/ by defaultService-Worker-Allowed response header// 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.
self.addEventListener("activate", (event) => {
event.waitUntil(
caches
.keys()
.then((keys) =>
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))),
),
);
});
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.
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);
})(),
);
});
// 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();
});
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 /.
response.clone() before both caching and returning, since body streams can only be read oncecache.add() will refuse them. Use cache.put() but you can't inspect the responseevent.waitUntil() synchronously within the event handler, not inside an async callbackService-Worker-Allowed header is setCache, CacheStorage, FetchEvent, Clients, ServiceWorkerRegistration, ServiceWorkerGlobalScope): references/api-reference.md