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.
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
- User installs a chat app
- App establishes WebSocket in main thread
- User minimizes app (iOS) or switches tabs (desktop)
- Browser throttles main thread timers
- WebSocket closes after inactivity timeout
- User returns to app: no new messages, needs reconnect
- User blames the app, deletes it
The good flow: service worker-managed WebSocket lifecycle
- PWA establishes WebSocket via service worker
- Service worker retains connection even when main thread sleeps
- Incoming messages queue in service worker storage
- User wakes app: message sync happens instant
- 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:
- Clear real-time toggle in settings
- Real-time data usage indicators
- Disconnect button that actually terminates the connection
- 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.