Service Worker mit Claude Code: Cache, Updates und Offline-UX
Praxisleitfaden für Service Worker mit Claude Code: Cache, Update-Zyklus, Offline-UX und lauffähige Beispiele.
Ein Service Worker ist für PWA- und Offline-Funktionen sehr nützlich, kann aber schnell zu Produktionsfehlern führen. Wenn du Claude Code nur bittest, “Caching einzubauen”, kann die App altes HTML ausliefern, private Kontodaten im Browser speichern oder nach einem Deployment nicht auf die neue Version wechseln.
Am einfachsten ist das Modell eines kleinen Proxys zwischen Browser und Server. Der Service Worker greift bei passenden Requests ein und entscheidet, ob die Antwort aus dem Netzwerk, aus der Cache API oder von einer Offline-Fallback-Seite kommt. Dieser Leitfaden zeigt die nötigen Entscheidungen, ein kopierbares Minimalbeispiel, Cache-Invalidierung, Update-Lifecycle, Offline-UX und konkrete Fehlerfälle.
Nutze dabei die offiziellen Quellen: MDN Service Worker API, web.dev Service Worker Guidance, web.dev Caching Guidance und Chrome Workbox docs. Passende interne Artikel sind der Claude Code PWA Guide, die Caching-Strategien und der IndexedDB Guide.
Aufgabe eines Service Workers
Ein Service Worker ist JavaScript außerhalb der Seite. Er kann nicht direkt auf das DOM zugreifen, also keine Buttons, Formulare oder React-States ändern. Er kann aber berechtigte Requests abfangen und mit Netzwerk, Cache oder Offline-Fallback beantworten.
Normales Seitenskript endet mit dem Tab. Ein Service Worker ist eventgesteuert: Der Browser weckt ihn für install, activate, fetch, push und manchmal Background Sync. Für die meisten Apps sind zuerst fetch, Cache API und der Update-Lifecycle wichtig.
sequenceDiagram
participant User as Nutzer
participant Page as Seite
participant SW as Service Worker
participant Cache as Cache API
participant Net as Server
User->>Page: Öffnet die Seite
Page->>SW: Registriert /sw.js
Page->>SW: Sendet fetch-Request
SW->>Cache: Prüft Cache
alt Im Cache
Cache-->>SW: Gespeicherte Antwort
else Nicht im Cache
SW->>Net: Holt frische Antwort
Net-->>SW: Neue Antwort
end
SW-->>Page: Antwort zum Rendern
Der Service Worker ist also keine Performance-Magie, sondern Verkehrssteuerung. Qualität entsteht durch klare Regeln: was gespeichert wird, wann es gelöscht wird und was Nutzer sehen, wenn das Netzwerk ausfällt.
Realistische Einsatzfälle
| Einsatzfall | Nutzen | Vorsicht |
|---|---|---|
| Dokumentation oder Blog | Artikel, CSS, Bilder und Fonts laden beim Wiederbesuch schneller | Zu lange gecachtes HTML versteckt Korrekturen |
| SaaS-Dashboard | Navigation und Grundlayout bleiben bei schwacher Verbindung sichtbar | Keine Billing-, Konto- oder Rechte-Daten cachen |
| Außendienstformular | Nutzer behalten Entwürfe und offene Aufgaben offline | POST in IndexedDB speichern, nicht in Cache API |
| Commerce- oder Medienkatalog | Thumbnails und Assets werden weniger oft geladen | Preise, Bestand und geschützte Bilder brauchen Frische-Regeln |
Masa hat das Muster an einer kleinen Lernseite getestet. Bilder und Fonts zu cachen verbesserte den Wiederbesuch spürbar. Artikel-HTML mit Cache First führte dagegen dazu, dass Textkorrekturen zu spät sichtbar wurden. Die Anweisung an Claude Code sollte deshalb nicht “alles cachen” heißen, sondern Ressourcen, Lebensdauer und Ausschlüsse benennen.
Guter Prompt für Claude Code
Service-Worker-Prompts sollten Verbote und Prüfungen enthalten.
Füge der bestehenden Vite-App einen Service Worker hinzu.
Anforderungen:
- /sw.js unter public ablegen und scope / verwenden
- Nur statische GET-Assets cachen
- HTML-Navigation mit Network First behandeln
- Bei Offline-Fehlern /offline.html zurückgeben
- API, POST, Auth-Seiten und andere Origins nicht cachen
- Datum oder Version in den Cache-Namen aufnehmen
- Alte Caches während activate löschen
- Reload-Hinweis zeigen, wenn ein neuer Worker waiting ist
Prüfung:
- Chrome DevTools > Application > Service Workers kontrollieren
- Network auf Offline stellen und /offline.html prüfen
- CACHE_VERSION ändern und Löschung alter Caches prüfen
Die Ausschlüsse sind entscheidend. Ein Cache-Fehler bei API oder privaten Seiten ist schnell ein Datenfehler.
Kopierbares Minimalbeispiel
Lege diese vier Dateien in einem leeren Ordner wie sw-demo ab und starte einen lokalen Server. Service Worker funktionieren nur über HTTPS oder localhost; ein direkt geöffnetes HTML reicht nicht.
python -m http.server 5173
Öffne http://localhost:5173.
<!-- index.html -->
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Service Worker Demo</title>
<style>
body {
font-family: system-ui, sans-serif;
margin: 2rem;
line-height: 1.7;
}
button {
padding: 0.7rem 1rem;
}
</style>
</head>
<body>
<h1>Service Worker Demo</h1>
<p id="status">Warte auf Registrierung.</p>
<button type="button" onclick="location.reload()">Neu laden</button>
<script src="/register-sw.js"></script>
</body>
</html>
// register-sw.js
const status = document.querySelector("#status");
let reloadRequested = false;
let updatePromptShown = false;
function setStatus(message) {
if (status) status.textContent = message;
}
function askToReload(worker) {
if (updatePromptShown) return;
updatePromptShown = true;
const ok = window.confirm(
"Eine neue Version ist verfügbar. Jetzt neu laden?",
);
if (ok) {
reloadRequested = true;
worker.postMessage({ type: "SKIP_WAITING" });
}
}
async function registerServiceWorker() {
if (!("serviceWorker" in navigator)) {
setStatus("Dieser Browser unterstützt keine Service Worker.");
return;
}
try {
const registration = await navigator.serviceWorker.register("/sw.js", {
scope: "/",
});
setStatus(`Service Worker registriert: ${registration.scope}`);
if (registration.waiting && navigator.serviceWorker.controller) {
askToReload(registration.waiting);
}
registration.addEventListener("updatefound", () => {
const worker = registration.installing;
if (!worker) return;
worker.addEventListener("statechange", () => {
const hasOldController = Boolean(navigator.serviceWorker.controller);
if (worker.state === "installed" && hasOldController) {
askToReload(worker);
}
});
});
} catch (error) {
console.error(error);
setStatus("Service-Worker-Registrierung fehlgeschlagen.");
}
}
navigator.serviceWorker?.addEventListener("controllerchange", () => {
if (!reloadRequested) return;
window.location.reload();
});
registerServiceWorker();
// sw.js
const CACHE_VERSION = "2026-06-02-v1";
const CACHE_PREFIX = "claude-sw-demo";
const CACHE_NAME = `${CACHE_PREFIX}-${CACHE_VERSION}`;
const APP_SHELL = [
"/",
"/index.html",
"/offline.html",
"/register-sw.js",
];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL)),
);
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((names) =>
Promise.all(
names
.filter((name) => name.startsWith(CACHE_PREFIX))
.filter((name) => name !== CACHE_NAME)
.map((name) => caches.delete(name)),
),
),
);
self.clients.claim();
});
self.addEventListener("message", (event) => {
if (event.data?.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
self.addEventListener("fetch", (event) => {
const { request } = event;
if (request.method !== "GET") return;
const url = new URL(request.url);
if (url.origin !== self.location.origin) return;
if (request.mode === "navigate") {
event.respondWith(networkFirstNavigation(request));
return;
}
if (["style", "script", "font", "image"].includes(request.destination)) {
event.respondWith(staleWhileRevalidate(request));
}
});
async function networkFirstNavigation(request) {
const cache = await caches.open(CACHE_NAME);
try {
const response = await fetch(request);
if (response.ok) cache.put(request, response.clone());
return response;
} catch {
return (
(await cache.match(request)) ||
(await cache.match("/offline.html")) ||
new Response("Offline", { status: 503 })
);
}
}
async function staleWhileRevalidate(request) {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);
const fetched = fetch(request)
.then((response) => {
if (response.ok) cache.put(request, response.clone());
return response;
})
.catch(() => cached || new Response("Offline", { status: 503 }));
return cached || fetched;
}
<!-- offline.html -->
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Offline</title>
</head>
<body>
<main>
<h1>Du bist offline</h1>
<p>Verbinde dich erneut und lade diese Seite neu.</p>
<button type="button" onclick="location.reload()">Erneut versuchen</button>
</main>
</body>
</html>
Das Beispiel nutzt Network First für Navigation und Stale While Revalidate für CSS, JavaScript, Fonts und Bilder. Network First fragt zuerst den Server und nutzt Cache oder offline.html nur bei Fehlern. Stale While Revalidate liefert sofort den Cache und aktualisiert im Hintergrund. Für Nachrichten, Preise, Bestand oder eingeloggte Seiten ist diese Strategie nicht automatisch geeignet.
Updates und Invalidierung
Der Update-Lifecycle ist die häufigste Fehlerquelle. Wenn sw.js geändert wird, installiert der Browser einen neuen Worker. Ist noch eine alte Seite offen, bleibt der neue Worker oft im Zustand waiting. Der Registrierungscode erkennt das, fragt den Nutzer und sendet SKIP_WAITING nur nach Zustimmung.
Der Worker ruft dann self.skipWaiting() auf, aktiviert sich, löscht alte Caches und übernimmt Clients. Ohne diesen Ablauf können Nutzer lange mit einem alten app-cache-v1 arbeiten.
Der Cache-Name sollte Datum, Release-Nummer oder Commit-ID enthalten. Wenn dein Build gehashte Dateien erzeugt, muss die Precache-Liste zur Build-Ausgabe passen. Wird die manuelle Liste zu fehleranfällig, hilft Workbox. Trotzdem entscheidet Workbox nicht, welche Geschäftsdaten gecacht werden dürfen.
Offline-UX
Offline-Unterstützung endet nicht bei einer Antwort aus Cache API. Nutzer müssen wissen, ob Arbeit gespeichert, zur Synchronisierung vorgemerkt oder fehlgeschlagen ist. Formulare speichern POST nicht in Cache API, sondern Entwürfe oder Jobs in IndexedDB und versuchen später erneut zu senden. Background Sync kann helfen, ist aber nicht überall gleich zuverlässig; wichtige Abläufe brauchen zusätzlich online-Event und sichtbaren Retry-Button.
Im Prompt an Claude Code sollten deshalb Offline-Text, Retry-Button, Entwurfszustände und Fehlermeldungen stehen. Eine Außendienst-App braucht mindestens “gesendet”, “auf diesem Gerät gespeichert” und “Synchronisierung fehlgeschlagen”.
Häufige Fehler
Erstens: falscher Scope. Ein Worker unter /app/sw.js kontrolliert standardmäßig /app/, nicht die ganze Website. Für volle Kontrolle liegt er unter /sw.js und wird mit scope / registriert.
Zweitens: 404 in cache.addAll(). Eine einzige fehlende URL lässt die gesamte Installation scheitern. Nach Änderungen durch Claude Code solltest du DevTools Application und Cache Storage prüfen.
Drittens: private Daten. Cache keine /api/me-Antworten, Billing-Seiten, Admin-HTML oder nutzerspezifisches JSON ohne klare Löschstrategie. Browsercache ist Speicher auf dem Gerät des Nutzers.
Viertens: keine Update-UX. Alte Worker können altes JS und CSS halten. Versioniere Cache-Namen, lösche während activate und lass Nutzer neu laden, wenn ein Worker wartet.
Außerdem ist ein Service Worker kein permanenter Speicher. Browser können Caches bei Platzmangel entfernen. Opaque Cross-Origin-Antworten sind schwer zu messen. DOM-Zugriff ist nicht möglich. Es funktioniert nur mit HTTPS oder localhost. Versprich kein “vollständiges Offline” ohne Test des echten Workflows.
Zusammenfassung und CTA
Ein Service Worker verbessert Wiederbesuche, Offline-Verhalten und PWA-Qualität. Sicher wird es, wenn Cache-Verantwortung, Update-Zyklus, Datenschutzregeln und Offline-Screens feststehen, bevor Claude Code Dateien ändert.
ClaudeCodeLab unterstützt PWA-Umstellungen, Cache-Design, Offline-Formulare, Workbox-Migration und Reviews von Claude-Code-Implementierungen. Wenn deine Website schneller werden soll, ohne alte oder private Daten auszuliefern, starte mit Claude Code Training und Beratung.
Beim Test dieser Minimalversion in Chrome erscheint nach dem ersten Laden claude-sw-demo-2026-06-02-v1 im Application-Panel. Mit Network auf Offline zeigt ein Reload offline.html. Wird CACHE_VERSION geändert, löscht activate die alten Caches. Damit eignet sich das Beispiel gut als Startpunkt für Release-Prüfungen.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Permission Safety Ladder: Zugriff kontrolliert erweitern
Von read-only zu begrenzten Änderungen, Prüfbefehlen und Deploy-Checks mit klarer Kontrolle.
Claude Code Small PR Proof Pack: kleine Änderungen reviewbar machen
Ein Proof Pack für Claude-Code-PRs: Diff, Checks, öffentliche URL, CTA-Pfad und Rollback.
Claude-Code-Review-Gate vor dem Commit
Vor dem Commit mit Claude Code prüfen: Diff, Build, öffentliche URL, Gumroad-Links, Beratung-CTA, fehlende Tests und fremde Dateien.