OpenPWAStore
Back to News
Guide · May 20, 2026

WebSockets need service worker awareness to stay alive in installed PWAs

Minimizing your PWA shouldn't break real-time features. Service workers make WebSocket persistence possible.

OpenPWA Editorial3 min read
WebSockets need service worker awareness to stay alive in installed PWAs cover

Why WebSocket lifecycle is an install problem

Users install real-time apps (chat, collaboration, live dashboards) expecting them to actually be real-time. When WebSocket connections die on minimize, background, or suspend, the app fails on the core promise of "always-on." The MDN Service Worker API docs show that service workers can maintain network contexts even when the main thread is suspended—critical for keeping WebSocket-based PWAs functional.

The bad flow: what happens without service worker integration

  1. User installs a chat app
  2. App establishes WebSocket in main thread
  3. User minimizes app (iOS) or switches tabs (desktop)
  4. Browser throttles main thread timers
  5. WebSocket closes after inactivity timeout
  6. User returns to app: no new messages, needs reconnect
  7. User blames the app, deletes it

The good flow: service worker-managed WebSocket lifecycle

  1. PWA establishes WebSocket via service worker
  2. Service worker retains connection even when main thread sleeps
  3. Incoming messages queue in service worker storage
  4. User wakes app: message sync happens instant
  5. App feels alive, user stays installed

Implementation patterns

Pattern 1: Service worker client routing

// In main thread
const ws = new WebSocket('wss://api.example.com/realtime');
ws.onmessage = (event) => {
  // Display message
};

// Better: Route through service worker
navigator.serviceWorker.addEventListener('message', (event) => {
  if (event.data.type === 'ws-message') {
    // Display message from service worker
  }
});

Pattern 2: Service worker WebSocket management

// In service worker
let wsConnection;

self.addEventListener('message', (event) => {
  if (event.data.action === 'connect') {
    wsConnection = new WebSocket(event.data.url);
    wsConnection.onmessage = (msg) => {
      // Store message in IndexedDB
      // Notify all clients
      self.clients.matchAll().then(clients => {
        clients.forEach(client => client.postMessage({
          type: 'ws-message',
          payload: msg.data
        }));
      });
    };
  }
});

Pattern 3: Background sync for reconnection

// In service worker
self.addEventListener('sync', (event) => {
  if (event.tag === 'websocket-reconnect') {
    event.waitUntil(reconnectWebSocket());
  }
});

async function reconnectWebSocket() {
  // Attempt reconnection with exponential backoff
}

Platform-specific WebSocket behaviors

iOS Safari

  • WebSocket connections throttled aggressively on minimize
  • Service worker keeps connection alive longer (~5-10 min)
  • Background fetch prevents complete connection loss
  • Requires user interaction background audio exception

Chrome/Edge (Desktop)

  • WebSocket throttled on tab background after ~30-120s
  • Service worker extends to ~5+ minutes before closing
  • Tab discarding occurs at resource limits
  • Notification permission extends background life

Android Chrome

  • Less aggressive throttling than iOS
  • Service worker maintains WebSocket across most states
  • Battery saver modes can still force close
  • Foreground service (via installed app wrapper) extends further

When WebSockets overdo it

Not every real-time feature needs WebSocket:

  • Polling works fine for updates every 30+ seconds
  • Push notifications are better for high-latency, low-frequency events
  • Server-Sent Events (SSE) simplify one-way streaming
  • Periodic Background Sync can batch offline updates

Implement WebSockets only when:

  • Sub-second latency genuinely matters (collaboration, gaming)
  • Two-way simultaneous communication is required
  • Connection security/privacy rules prevent polling
  • User explicitly expects "live" behavior

Privacy and permissions constraints

Real-time connections have edge cases:

  • WebSocket data bypasses standard cache clearing
  • IndexedDB-stored messages persist across site data wipes
  • User-driven logout must close connection explicitly
  • User agent throttling bypass may trigger privacy warnings

Always provide:

  1. Clear real-time toggle in settings
  2. Real-time data usage indicators
  3. Disconnect button that actually terminates the connection
  4. Privacy notice explaining persistence behavior

Measurement checklist

Monitor WebSocket lifecycle with:

  • Connection duration (before throttle)
  • Reconnection success rate after wake
  • Message delivery latency (main thread vs service worker)
  • Battery impact with persistent connections
  • Data usage growth with real-time enabled

Next step

Start with a simple service worker reconnection pattern, measure connection stability, then upgrade to full service worker-managed WebSocket routing for mission-critical real-time features.