Background Sync API: Offline-Aktionen automatisch nachholen
AI generated
JS
() =>
JavaScript · Browser-APIs · Offline-First
Background Sync API
Offline-Aktionen automatisch nachholen

Ein Formular wird abgeschickt, aber die Verbindung ist gerade weg. Statt eine Fehlermeldung zu zeigen und die Eingabe zu verwerfen, merkt sich die Background Sync API die Aktion und liefert sie zuverlaessig nach, sobald das Netz zurueck ist, auch wenn der Tab laengst geschlossen wurde.

16 Min. Lesezeit Background Sync API Service Worker Offline-First IndexedDB

1. Das Problem: Offline-Aktionen gehen sonst einfach verloren

Ohne Background Sync haben Web-Apps genau zwei schlechte Optionen, wenn ein Nutzer offline eine Aktion ausloest: entweder die Anfrage sofort scheitern lassen und eine Fehlermeldung zeigen, oder still versuchen, per fetch() zu senden, und hoffen, dass die Promise irgendwann doch aufgeloest wird. Beides fuehrt zu Frust, insbesondere bei mobilen Nutzern mit instabiler Verbindung in Aufzuegen, Tunneln oder auf dem Land.

Die Background Sync API loest das strukturell: Statt die Anfrage direkt zu senden, wird die Absicht registriert, etwa 'sende dieses Formular ab', und der Browser uebernimmt die Verantwortung, diese Absicht zuzustellen, sobald wieder eine Verbindung besteht, selbst wenn der Nutzer die Seite laengst verlassen hat. Der Service Worker wird dafuer vom Betriebssystem geweckt, unabhaengig vom Lebenszyklus des Tabs.

2. Einen Sync-Task registrieren

Der Ablauf beginnt im Hauptthread: Statt fetch() direkt aufzurufen, wird die Aktion zunaechst lokal gespeichert, meist in IndexedDB, und anschliessend ein Sync-Tag ueber registration.sync.register() beim Service Worker angemeldet. Dieses Tag ist ein frei waehlbarer String, der spaeter im Service Worker identifiziert, welche Art von Aktion nachgeholt werden soll.

Wichtig ist, dass register() selbst keine Garantie fuer sofortige Ausfuehrung gibt. Ist die Verbindung bereits vorhanden, feuert das sync-Event meist innerhalb weniger Sekunden, ist sie es nicht, wartet der Browser, bis das Betriebssystem eine stabile Verbindung meldet, und kann dabei durchaus Batterie- und Netzwerk-Heuristiken einbeziehen, die Entwickler nicht direkt beeinflussen koennen.


// app.js: Formular offline-sicher abschicken
async function submitComment(formData) {
  await saveToOutbox(formData); // IndexedDB, siehe naechster Abschnitt

  if ('serviceWorker' in navigator && 'SyncManager' in window) {
    const registration = await navigator.serviceWorker.ready;
    try {
      await registration.sync.register('sync-comments');
    } catch (err) {
      // Background Sync nicht verfuegbar -> sofortiger Fallback-Versuch
      await trySendDirectly(formData);
    }
  } else {
    await trySendDirectly(formData);
  }
}

3. Die Outbox: Aktionen in IndexedDB zwischenspeichern

Background Sync transportiert selbst keine Daten, es liefert lediglich ein Signal 'jetzt synchronisieren'. Die eigentlichen Nutzdaten, also etwa der Formularinhalt oder die Like-Aktion, muessen deshalb vorher in einem persistenten Speicher abgelegt werden, der sowohl vom Hauptthread als auch vom Service Worker erreichbar ist. IndexedDB ist dafuer die naheliegende Wahl, da localStorage im Service-Worker-Kontext nicht verfuegbar ist.

Ueblich ist ein Outbox-Muster: Ein Object Store haelt ausstehende Aktionen mit Status 'pending', der Service Worker liest beim sync-Event alle offenen Eintraege, versucht sie zu senden und markiert erfolgreiche Eintraege als erledigt oder loescht sie direkt. So bleibt der Zustand konsistent, selbst wenn zwischenzeitlich mehrere Sync-Versuche noetig sind.


// outbox.js: einfache IndexedDB-Outbox
const DB_NAME = 'outbox-db';
const STORE = 'pending-comments';

function openOutbox() {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, 1);
    req.onupgradeneeded = () => {
      req.result.createObjectStore(STORE, { keyPath: 'id', autoIncrement: true });
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}

async function saveToOutbox(data) {
  const db = await openOutbox();
  return new Promise((resolve, reject) => {
    const tx = db.transaction(STORE, 'readwrite');
    tx.objectStore(STORE).add({ ...data, createdAt: Date.now() });
    tx.oncomplete = () => resolve();
    tx.onerror = () => reject(tx.error);
  });
}

4. Das sync-Event im Service Worker behandeln

Feuert das sync-Event, liegt event.tag mit dem registrierten Tag vor, sodass ein Service Worker mehrere unterschiedliche Sync-Aufgaben unterscheiden kann, etwa 'sync-comments' fuer Kommentare und 'sync-likes' fuer Like-Klicks. Der Handler liest alle offenen Eintraege aus der Outbox, sendet sie sequenziell oder parallel an den Server und entfernt erfolgreich verarbeitete Eintraege.

Genau wie beim push-Event muss auch hier event.waitUntil() verwendet werden, damit der Service Worker nicht vorzeitig terminiert wird, bevor alle ausstehenden Anfragen abgeschlossen sind. Schlaegt der Versand erneut fehl, etwa weil die Verbindung sofort wieder abbricht, sollte die Promise mit einem Fehler abgelehnt werden, denn dann registriert der Browser automatisch einen erneuten Versuch mit exponentiellem Backoff.


// sw.js
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-comments') {
    event.waitUntil(flushOutbox());
  }
});

async function flushOutbox() {
  const pending = await getAllFromOutbox();

  for (const item of pending) {
    try {
      const res = await fetch('/api/comments', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(item),
      });
      if (!res.ok) throw new Error('Server lehnte Kommentar ab');
      await removeFromOutbox(item.id);
    } catch (err) {
      // Nicht aus der Outbox entfernen -> naechster sync-Versuch greift erneut
      throw err;
    }
  }
}

5. Browser-Unterstuetzung und Feature Detection

Die Background Sync API ist bislang auf Chromium-basierte Browser beschraenkt, Safari und Firefox unterstuetzen sie nicht. Das macht ein sauberes Feature Detection zwingend: Vor jedem register()-Aufruf muss geprueft werden, ob sowohl 'serviceWorker' in navigator als auch 'SyncManager' in window vorhanden sind, sonst wirft der Aufruf eine Exception.

Fuer nicht unterstuetzende Browser braucht es einen Fallback, der die Aktion trotzdem irgendwann nachholt, etwa ein eigener online-Event-Listener, der bei Rueckkehr der Verbindung manuell versucht, ausstehende Outbox-Eintraege zu senden. So bleibt die Grundfunktion auch ohne native API erhalten, wenn auch ohne die Zuverlaessigkeit der Betriebssystem-Integration.


// Fallback fuer Browser ohne Background Sync
function registerFallbackSync() {
  window.addEventListener('online', async () => {
    const pending = await getAllFromOutbox();
    for (const item of pending) {
      await trySendDirectly(item);
    }
  });
}

const supportsBackgroundSync =
  'serviceWorker' in navigator && 'SyncManager' in window;

if (!supportsBackgroundSync) {
  registerFallbackSync();
}

6. Abgrenzung zu Periodic Background Sync

Neben der regulaeren Background Sync API existiert die verwandte, aber deutlich restriktivere Periodic Background Sync API, die periodische Aktualisierungen im Hintergrund erlaubt, etwa das Nachladen neuer Inhalte alle paar Stunden. Sie loest ein anderes Problem: nicht das Nachholen einer konkreten Nutzeraktion, sondern das proaktive Aktualisieren von Daten, ohne dass der Nutzer die App geoeffnet hat.

Periodic Background Sync verlangt zusaetzlich, dass die Web-App installiert wurde und eine gewisse 'Site Engagement Score' erreicht, was sie fuer die meisten Projekte deutlich unzuverlaessiger macht als die regulaere Background Sync API. Fuer das Nachholen konkreter Nutzeraktionen wie Formular-Absenden bleibt die einmalige, ereignisgetriebene Background Sync API die richtige Wahl.

7. Konfliktbehandlung: was bei zeitversetztem Versand passieren kann

Wird eine Aktion erst Stunden nach der eigentlichen Nutzereingabe nachgeholt, koennen sich zwischenzeitlich die Rahmenbedingungen geaendert haben: ein Like-Ziel existiert nicht mehr, ein Warenkorb-Preis hat sich geaendert, oder der Nutzer hat sich zwischenzeitlich ausgeloggt. Der Server-Endpunkt fuer nachgeholte Aktionen sollte deshalb defensiv programmiert sein und klare Fehlercodes fuer solche Faelle liefern.

Auf Client-Seite empfiehlt sich, jeden Outbox-Eintrag mit einem Zeitstempel zu versehen und bei der Verarbeitung im Service Worker zu pruefen, ob die Aktion ueberhaupt noch sinnvoll ist, etwa durch ein Ablaufdatum. Ein vor drei Tagen offline gesetzter Like sollte im Zweifel eher verworfen als stillschweigend Stunden nach dem eigentlichen Klick zugestellt werden, wenn der Kontext fuer den Nutzer laengst nicht mehr relevant ist.

8. Nutzer-Feedback: Transparenz ueber ausstehende Aktionen

Aus Nutzersicht ist es wichtig, sichtbar zu machen, dass eine Aktion zwar angenommen, aber noch nicht endgueltig zugestellt wurde. Ein einfaches UI-Signal, etwa ein kleines Wolken-Icon mit 'wird gesendet, sobald wieder Netz da ist' neben dem Kommentar, verhindert Verwirrung und wiederholtes Absenden derselben Aktion aus Ungeduld.

Der Service Worker kann per postMessage() an offene Tabs melden, wenn ein Outbox-Eintrag erfolgreich verarbeitet wurde, sodass die UI in Echtzeit aktualisiert werden kann, falls der Nutzer die Seite noch offen hat. Ist die Seite geschlossen, uebernimmt beim naechsten Besuch ein Abgleich zwischen lokalem und Server-Zustand die Aktualisierung der Anzeige.


// sw.js: Tabs ueber erfolgreichen Sync informieren
async function notifyClients(message) {
  const allClients = await clients.matchAll({ type: 'window' });
  for (const client of allClients) {
    client.postMessage(message);
  }
}

// nach erfolgreichem Versand innerhalb von flushOutbox():
await notifyClients({ type: 'sync-success', id: item.id });

9. Fazit: Zuverlaessigkeit statt Fehlermeldung

Die Background Sync API verschiebt die Verantwortung fuer eine zuverlaessige Zustellung vom Nutzer weg hin zum Browser: Statt eine fehlgeschlagene Anfrage manuell erneut anzustossen, merkt sich die App die Absicht und der Browser garantiert die Zustellung, sobald es die Netzwerksituation zulaesst. Kombiniert mit einer IndexedDB-Outbox entsteht so ein robustes Offline-First-Verhalten ohne komplexe eigene Retry-Logik.

Wegen der eingeschraenkten Browser-Unterstuetzung bleibt ein Fallback fuer Safari und Firefox unverzichtbar, und Entwickler sollten Nutzern stets transparent machen, dass eine Aktion noch aussteht. Die folgende Tabelle stellt die zentralen Bausteine im Ueberblick dar.

Baustein Ort Aufgabe Wichtiges Detail
Outbox (IndexedDB) Client + Service Worker Nutzdaten persistent zwischenspeichern localStorage funktioniert im Service Worker nicht
sync.register() Client Sync-Task anmelden Keine Garantie fuer sofortige Ausfuehrung
sync-Event Service Worker Ausstehende Eintraege verarbeiten event.waitUntil() zwingend erforderlich
Fehlgeschlagener Versand Service Worker Automatischer Retry Promise ablehnen loest erneuten Versuch aus

Mironsoft

Moderne Browser-APIs, Performance und wartbares JavaScript

JavaScript, das im echten Browser robust bleibt, nicht nur im Tutorial?

Wir prüfen bestehenden Frontend-Code auf veraltete Patterns, unnötige Bibliotheken und Performance-Fallen und ersetzen sie durch moderne, native Browser-APIs, die weniger Bundle-Gewicht und weniger Wartungslast bedeuten.

Code-Review

Veraltete Patterns, unnötige Dependencies und Memory Leaks systematisch aufspüren.

Performance-Optimierung

Bundle-Größe, Ladezeit und Runtime-Performance mit modernen APIs verbessern.

Modernisierung

Native Browser-APIs statt schwerer Bibliotheken gezielt einführen.

10. Zusammenfassung

Background Sync API: Das Wichtigste auf einen Blick

Signal statt Daten

Background Sync liefert nur das 'jetzt synchronisieren'-Signal, Daten liegen in IndexedDB

Registrierung

registration.sync.register() meldet einen Sync-Task mit frei waehlbarem Tag an

Verarbeitung

Service Worker liest die Outbox im sync-Event und sendet ausstehende Eintraege nach

Support

Nur Chromium-Browser, Fallback ueber online-Event fuer Safari und Firefox noetig

11. FAQ: Background Sync API: Das Wichtigste auf einen Blick

1Was macht die Background Sync API genau?
Sie registriert eine Absicht, etwa 'sende dieses Formular ab', und garantiert, dass der Service Worker geweckt wird, sobald wieder eine Netzwerkverbindung besteht, selbst wenn der Tab bereits geschlossen ist.
2Transportiert Background Sync selbst Daten?
Nein. Die API liefert nur ein Signal an den Service Worker. Die eigentlichen Nutzdaten muessen vorher in einem persistenten Speicher wie IndexedDB abgelegt werden.
3Warum eignet sich localStorage nicht als Outbox?
localStorage ist im Service-Worker-Kontext nicht verfuegbar und zudem synchron, was das Event-Loop-Modell des Service Workers blockieren wuerde. IndexedDB ist asynchron und von beiden Kontexten erreichbar.
4Was passiert, wenn der Versand im sync-Event erneut fehlschlaegt?
Wird die Promise im Handler abgelehnt, registriert der Browser automatisch einen erneuten Versuch mit exponentiellem Backoff, ohne dass die App das selbst implementieren muss.
5Welche Browser unterstuetzen Background Sync?
Aktuell nur Chromium-basierte Browser wie Chrome und Edge. Safari und Firefox unterstuetzen die API nicht, weshalb ein Fallback ueber den online-Event noetig ist.
6Was ist der Unterschied zu Periodic Background Sync?
Periodic Background Sync aktualisiert Daten proaktiv in regelmaessigen Abstaenden, waehrend die regulaere Background Sync API eine konkrete, einmalige Nutzeraktion nachholt.
7Wie erfaehrt der Nutzer, dass eine Aktion noch aussteht?
Ueber ein eigenes UI-Signal, etwa ein Icon mit Hinweistext, das sichtbar macht, dass die Aktion angenommen, aber noch nicht zugestellt wurde.
8Kann der Service Worker die offene Seite ueber erfolgreichen Sync informieren?
Ja, ueber clients.matchAll() und postMessage() kann der Service Worker offene Tabs in Echtzeit benachrichtigen, sobald ein Outbox-Eintrag erfolgreich verarbeitet wurde.
9Was tun bei veralteten Outbox-Eintraegen?
Jeder Eintrag sollte einen Zeitstempel erhalten, damit der Service Worker beim Verarbeiten pruefen kann, ob die Aktion noch relevant ist, und veraltete Eintraege im Zweifel verwirft.
10Ist Background Sync fuer sicherheitskritische Aktionen geeignet?
Fuer unkritische Aktionen wie Kommentare oder Likes ja. Bei zahlungsrelevanten Vorgaengen sollte der Server zusaetzliche Validierung und Idempotenz-Pruefungen vornehmen, da der genaue Zustellzeitpunkt nicht vorhersehbar ist.