How Background Sync API Defers Offline Actions

The Background Sync API is a web standard that allows web applications to defer network operations until the user has a stable internet connection. By delegating pending tasks—such as chat messages, form submissions, or analytics logs—to a Service Worker, the API ensures that user actions are reliably executed in the background, even if the user navigates away from the page or closes the browser tab before connectivity is restored.

The Core Architecture

The Background Sync API relies on three main components working together:

  1. Client-Side Application: Detects offline conditions or failed requests and registers a sync tag.
  2. IndexedDB: Stores the serialized payload (e.g., request body, headers, URLs) locally while offline.
  3. Service Worker: Listens for the sync event triggered by the browser once connectivity is restored and executes the deferred fetch requests.

Step-by-Step Implementation Flow

1. Storing Offline Data

When a user attempts a network action while offline, the application catches the failure and writes the request data into IndexedDB to preserve it across sessions.

// Storing data to IndexedDB when offline
async function saveRequestLocally(data) {
  const db = await openDatabase(); // Utility function to open IndexedDB
  const tx = db.transaction('offline-queue', 'readwrite');
  await tx.objectStore('offline-queue').add(data);
}

2. Registering a Sync Event

Once the data is saved, the client requests a sync event through the active Service Worker registration using a descriptive tag.

async function registerBackgroundSync() {
  if ('serviceWorker' in navigator && 'SyncManager' in window) {
    const registration = await navigator.serviceWorker.ready;
    try {
      await registration.sync.register('sync-deferred-posts');
      console.log('Background sync registered.');
    } catch (error) {
      console.error('Background sync registration failed:', error);
    }
  } else {
    // Fallback: Attempt immediate sync when navigator.onLine fires
    window.addEventListener('online', sendFallbackRequest);
  }
}

3. Handling the Sync Event in the Service Worker

Inside the Service Worker script (sw.js), listen for the sync event. The browser automatically fires this event as soon as it detects a network connection.

// sw.js (Service Worker)
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-deferred-posts') {
    event.waitUntil(processDeferredRequests());
  }
});

async function processDeferredRequests() {
  const db = await openDatabase();
  const queue = await db.getAll('offline-queue');

  for (const item of queue) {
    try {
      const response = await fetch('/api/submit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(item.data)
      });

      if (response.ok) {
        // Remove processed item from IndexedDB
        await db.delete('offline-queue', item.id);
      }
    } catch (error) {
      // If the fetch fails, throw error to signal retry
      throw error;
    }
  }
}

Key Behaviors and Lifecycle Rules