Deployment live, Browser alt: Service-Worker-Updates ohne Versionschaos
Ein neues Release ist online, aber offene Tabs können alten Code behalten. So prüfen Sie Service-Worker-Lifecycle, Cache-Versionen, skipWaiting und gemischte Deployments.
Ein Deployment ist erfolgreich. CI ist grün, das CDN liefert die neue Version und der Fehler ist im frischen Browser nicht mehr reproduzierbar.
Trotzdem meldet ein Teil der Nutzer weiterhin den alten Fehler. Andere sehen plötzlich eine leere Seite, sobald sie einen selten genutzten Bereich öffnen. Im Support entsteht die unangenehme Situation: „Bei uns funktioniert es.“
Wenn eine Website oder PWA einen Service Worker nutzt, kann genau das technisch korrekt sein. „Neue Version ist deployed“ bedeutet nicht automatisch „jeder offene Browser läuft bereits auf derselben Version“.
Service Worker haben einen eigenen Lebenszyklus. Alte Tabs können weiter von einem aktiven Worker kontrolliert werden, während eine neue Version bereits installiert wurde und wartet. Caches können Dateien aus früheren Releases enthalten. Ein aggressiver Sofortwechsel kann wiederum dazu führen, dass ein neues Service-Worker-Skript eine Seite übernimmt, deren JavaScript noch aus dem alten Release stammt.
Das ist kein exotischer PWA-Sonderfall. Es ist Release Engineering im Browser.
Warum ein Browser nach dem Deployment noch alt sein kann
Wenn der Browser eine geänderte Service-Worker-Datei findet, ersetzt er den aktuell aktiven Worker nicht sofort. Die neue Version wird zunächst installiert. Wenn bereits Seiten vom bisherigen Worker kontrolliert werden, geht der neue Worker standardmäßig in den Zustand waiting.
Erst wenn der alte Worker keine Clients mehr kontrolliert, kann die neue Version aktiv werden.
Dieses Verhalten ist absichtlich konservativ. Eine bereits geöffnete Anwendung soll nicht mitten in einer Sitzung unter ihr ausgetauscht werden. Das schützt vor einem besonders unangenehmen Zustand: Das HTML und JavaScript im Tab gehören zu Release A, während Netzwerkrequests plötzlich von Service Worker B behandelt werden.
Das bedeutet zugleich: Ein Nutzer, der ein Dashboard tagelang offen lässt, kann länger auf einer alten Client-Version bleiben als das Deployment-Team erwartet.
Das eigentliche Risiko ist nicht „alter Cache“, sondern gemischte Versionen
„Cache leeren“ ist eine beliebte Universalantwort. Sie überspringt aber die interessantere Frage: Welche Versionen dürfen gleichzeitig existieren, und sind sie miteinander kompatibel?
Ein realistischer Zustand nach einem Release kann so aussehen:
- Der Server und die API laufen bereits auf Version B.
- Ein offener Tab enthält noch HTML und JavaScript von Version A.
- Service Worker A kontrolliert diesen Tab.
- Service Worker B ist bereits installiert und wartet.
- Cache A enthält alte statische Assets.
- Cache B wurde während der Installation vorbereitet.
- Ein zweiter, frisch geöffneter Browser kann bereits einen anderen Zustand sehen.
Das System muss diesen Übergang aushalten können.
Der verlockende Shortcut: skipWaiting()
Mit self.skipWaiting() kann ein neu installierter Service Worker die Wartephase überspringen und schneller aktiv werden.
Das kann sinnvoll sein, ist aber keine kostenlose „immer die neueste Version“-Option. Die Chrome- und web.dev-Dokumentation warnt ausdrücklich vor gemischten Versionen: Der neue Worker kann Seiten kontrollieren, die unter dem vorherigen Worker geladen wurden.
Ein typischer Fehler sieht dann so aus:
Release A hat beim ersten Laden app-A.js geladen. Ein seltener Dialog wird später per Dynamic Import aus dialog-A.js nachgeladen.
Release B wird deployed. Der neue Worker wird sofort aktiviert und alte Assets werden gleichzeitig gelöscht.
Der Nutzer klickt nun im noch offenen Release-A-Tab auf den Dialog. Die Seite verlangt dialog-A.js. Der neue Worker oder das CDN kennt aber nur noch dialog-B.js.
Ergebnis: Chunk-Fehler, weißer Bereich oder ein Reload-Loop – obwohl das neue Release an sich funktioniert.
Sofortige Aktivierung ist deshalb eine Produktentscheidung über Versionskompatibilität, nicht nur eine Service-Worker-Zeile.
Ein sichereres Update-Modell
Für viele geschäftliche Websites und SaaS-Oberflächen ist ein kontrollierter Übergang stabiler als ein erzwungener Sofortwechsel.
Neue Version erkennen
Die Registrierung stellt Zustände wie installing, waiting und active bereit. Über updatefound lässt sich erkennen, dass ein neuer Worker installiert wird. Bei lang geöffneten Anwendungen kann registration.update() bewusst eine Update-Prüfung anstoßen.
Das ist besonders relevant für Dashboards, Kassenoberflächen, Admin-Tools oder SaaS-Produkte, die nicht täglich vollständig geschlossen werden.
Auf einen sicheren Wechselpunkt warten
Wenn ein neuer Worker wartet, kann die Anwendung eine unaufdringliche Meldung anzeigen: „Eine neue Version ist verfügbar. Jetzt aktualisieren.“
Der Nutzer kann einen laufenden Formularvorgang, Upload oder Editorzustand zunächst beenden. Danach wird die neue Version aktiviert und die Seite kontrolliert neu geladen. Für Anwendungen mit ungespeicherten Daten ist das häufig besser als ein überraschender Reload mitten in der Arbeit.
Aktivierung bewusst beobachten
controllerchange signalisiert, dass sich der kontrollierende Service Worker geändert hat. Dieser Punkt eignet sich, um einen geplanten Reload auszulösen – aber nur einmal und nur dann, wenn die Anwendung den Wechsel tatsächlich angefordert hat. Sonst entstehen leicht Reload-Schleifen oder schwer reproduzierbare Zustände.
Cache-Versionierung gehört zum Release-Prozess
Cache Storage verwaltet benannte Caches. Alte Caches verschwinden nicht automatisch nur deshalb, weil ein neues Deployment existiert.
Ein übliches Muster ist deshalb:
- Cache-Namen an eine Release- oder Schema-Version binden,
- benötigte Ressourcen während
installvorbereiten, - alte, eindeutig nicht mehr benötigte Cache-Versionen während
activateentfernen, - Laufzeit-Caches getrennt von statischen Precache-Dateien behandeln,
- keine Datenarten cachen, deren Aktualitätsregeln nicht klar definiert sind.
Wichtig ist der Zeitpunkt der Bereinigung. Wer mit skipWaiting() sofort übernimmt und gleichzeitig sämtliche alten Dateien löscht, kann noch geöffnete alte Dokumente beschädigen. Wer dagegen den normalen Wartezustand respektiert, hat beim Aktivieren bereits eine sauberere Grenze: Der vorherige Worker kontrolliert keine Clients mehr.
Die Service-Worker-URL nicht pro Release wechseln
Eine häufige Idee lautet, den Dateinamen selbst zu versionieren: aus /sw.js wird /sw-v42.js.
web.dev rät davon ab, den registrierten Service-Worker-Pfad als Release-Version zu behandeln. Ein alter, gecachter HTML-Stand kann weiterhin den alten Pfad registrieren. Dann existieren plötzlich mehrere Registrierungen oder alte Clients bleiben an einer Update-Kette hängen, die gar nicht mehr gepflegt wird.
Besser ist ein stabiler Registrierungs-Pfad wie /sw.js. Sein Inhalt ändert sich; der Browser erkennt die Änderung und führt den vorgesehenen Update-Lebenszyklus aus. Versionsnummern gehören in Cache-Namen, Manifeste oder Build-Metadaten – nicht zwingend in die Identität der Registrierung.
Deployments brauchen eine Asset-Übergangsstrategie
Ein Service Worker ist nur eine Hälfte des Problems. Die andere Hälfte ist das CDN oder Hosting.
Wenn Release B alle gehashten Assets aus Release A sofort löscht, kann ein alter Tab auch ohne Service Worker später scheitern, sobald er einen noch nicht geladenen Chunk anfordert. Eine robuste Deployment-Strategie behält deshalb frühere, unveränderliche Build-Artefakte für ein sinnvolles Übergangsfenster. Gehashte Dateien können nebeneinander existieren, weil ihr Inhalt über den Dateinamen eindeutig ist.
Das reduziert den Druck, jeden offenen Client im selben Moment aktualisieren zu müssen.
Auch die API muss den Übergang aushalten
Der gefährlichste Versionskonflikt ist oft nicht JavaScript gegen JavaScript, sondern Client A gegen API B.
Ein neues Backend entfernt ein Feld, benennt einen Endpoint um oder ändert eine Validierungsregel. Alte Browser-Tabs senden aber noch Requests nach dem alten Vertrag. Daraus folgt eine praktische Release-Regel: Frontend- und API-Änderungen sollten mindestens über das realistische Client-Update-Fenster rückwärtskompatibel sein.
Bei unvermeidbaren Breaking Changes braucht es eine explizite Versionsgrenze, eine Update-Sperre oder einen kontrollierten „Bitte neu laden“-Pfad. Sonst wird ein Service-Worker-Updateproblem zum Daten- oder Workflowproblem.
Der Test, den kaum ein Happy-Path-Test abdeckt
Ein sinnvoller Release-Test beginnt absichtlich mit einer alten Version.
- Version A öffnen und einen Tab aktiv lassen.
- Einen zweiten Tab öffnen und beide vom selben Worker kontrollieren lassen.
- Version B deployen.
- Prüfen, ob B erkannt und als
waitingsichtbar wird. - Im alten Tab Navigation, Dynamic Imports, Formulare und API-Aktionen ausführen.
- Einen Tab schließen und beobachten, ob der andere weiterhin stabil bleibt.
- Den Update-Wechsel bewusst auslösen.
- Prüfen, ob
controllerchange, Reload und gespeicherter Nutzerzustand korrekt zusammenarbeiten. - Offline- und langsame Netzwerkbedingungen testen.
- Einen fehlerhaften neuen Service Worker simulieren und prüfen, ob der bisherige Worker aktiv bleibt.
- Cache Storage nach mehreren Releases untersuchen.
- Einen lange offenen Tab Stunden oder Tage später erneut testen.
Dieser Ablauf prüft nicht nur, ob die PWA „installierbar“ ist. Er prüft, ob Releases unter realen Lebenszyklen überleben.
Welche Symptome auf ein Update-Problem hindeuten
Ein paar Fehlerbilder sind besonders verdächtig:
- Ein Bug ist nur nach „Hard Reload“ weg.
- Einige Nutzer sehen neue UI, andere dauerhaft die alte.
- Dynamic Imports schlagen nach Deployments mit Chunk-404s fehl.
- Ein Reload behebt plötzlich API- oder Formularfehler.
- Support kann den Fehler nur in lange offenen Tabs reproduzieren.
- Cache Storage enthält viele alte, nie entfernte Versionen.
- Die Anwendung registriert mehrere Service-Worker-Pfade für denselben Bereich.
- Ein Update-Prompt erscheint, aktualisiert aber ohne Rücksicht auf ungespeicherte Arbeit.
- Nach jedem Release hilft nur „Website-Daten löschen“.
Der letzte Punkt ist besonders wichtig: „Bitte Cache löschen“ ist ein Notfall-Workaround, keine Update-Architektur.
Was Website-Pflichtencheck prüfen würde
Ein technischer Service-Worker- und Release-Check kann untersuchen:
- Registrierung, Scope und stabilen Worker-Pfad,
- Update-Erkennung und
registration.update(), - Zustände
installing,waitingundactive, - Einsatz von
skipWaiting()undclients.claim(), - Verhalten bei mehreren offenen Tabs,
controllerchangeund Reload-Logik,- Versionierung und Bereinigung von Cache Storage,
- Cache-Strategien für HTML, statische Assets und Laufzeitdaten,
- Aufbewahrung älterer gehashter Assets nach Deployments,
- Dynamic Imports und Chunk-Fehler,
- Kompatibilität zwischen alten Clients und neuer API,
- Offline-Fallbacks,
- Verhalten nach fehlgeschlagenen Service-Worker-Installationen,
- Update-Hinweise und Schutz ungespeicherter Nutzerdaten,
- Regressionstests über mindestens zwei Release-Versionen.
Das Ziel ist nicht, Service Worker grundsätzlich zu vermeiden. Richtig eingesetzt liefern sie Offline-Fähigkeit, kontrolliertes Caching und schnelle wiederholte Nutzung. Aber sie machen den Browser zu einem Teil Ihrer Release-Infrastruktur.
Wenn ein Deployment nur dann zuverlässig ist, wenn jeder Nutzer sofort alle Tabs schließt und den Cache leert, ist nicht der Nutzer „zu lange auf der Seite“. Der Update-Pfad ist unvollständig.