Real-Time Communication in PWAs: WebSockets vs Server-Sent Events vs WebRTC

Real-time communication is what makes a Progressive Web App feel like an app rather than a website. Live updates, instant messages, collaborative editing, notifications that arrive the moment something happens — all of it depends on the application receiving data the instant it changes, without the user refreshing anything.
Three technologies cover virtually every real-time scenario in PWAs: WebSockets, Server-Sent Events (SSE), and WebRTC. They are not interchangeable — each makes different trade-offs, and picking the wrong one means either over-engineering a simple feature or fighting a technology that was never designed for your use case. This guide covers how each works, when to use which, and the implementation questions that come up in practice.
Why Real-Time Matters in PWAs
Traditional web applications follow a request-response model: the client asks, the server answers, and nothing happens in between. To simulate live updates, clients resort to polling — repeatedly asking “anything new?” — which wastes bandwidth, adds latency, and hammers the server with mostly-empty responses.
Real-time technologies replace polling with persistent connections. The practical wins:
- Instant updates without refreshes — content changes appear as they happen, which matters for messaging, dashboards, live feeds, and collaborative tools.
- Lower latency and less traffic — data is pushed only when there is something to push, eliminating the empty round trips of polling.
- Higher engagement — real-time notifications and live interaction are consistently among the strongest retention levers in product analytics.
Typical scenarios: collaborative document editing, instant messaging, live sports scores and stock tickers, multiplayer games, shared whiteboards, real-time inventory and price updates in eCommerce, and live delivery tracking.
WebSockets: Bidirectional, Low-Latency, the Default Choice
WebSocket is a protocol providing full-duplex communication over a single TCP connection. After an HTTP handshake upgrades the connection, both the client and server can send messages at any time, for as long as the connection stays open.
The handshake works like this: the client sends an HTTP request with an Upgrade: websocket header; if the server agrees, it responds with status 101 Switching Protocols, and from that point the connection speaks the WebSocket protocol — no more HTTP headers, minimal per-message overhead.
const socket = new WebSocket('wss://example.com/socket');
socket.addEventListener('open', () => {
socket.send('Hello, server!');
});
socket.addEventListener('message', (event) => {
console.log('Message from server:', event.data);
});
socket.addEventListener('close', (event) => {
if (!event.wasClean) {
// Connection died — schedule a reconnect with backoff
}
});Use WebSockets when communication is genuinely two-way and frequent: chat, multiplayer games, collaborative editing, trading interfaces, live dashboards where the client also sends commands.
What to plan for in production:
- Reconnection logic — mobile networks drop connections constantly. Implement reconnects with exponential backoff and jitter; never assume the socket stays open.
- Heartbeats — proxies and load balancers silently kill idle connections. Periodic ping/pong frames keep them alive and detect dead peers.
- Scaling — each open socket holds server memory. At scale you need horizontal scaling with a pub/sub backplane (typically Redis) so a message published on one server reaches clients connected to another. This is the same architecture problem we cover in our message broker comparison — the broker behind your sockets matters as much as the sockets.
- State recovery — after a reconnect, the client must catch up on missed messages. Design your protocol with sequence numbers or a “fetch since timestamp” endpoint from day one.
Server-Sent Events: One-Way Push, Radically Simple
Server-Sent Events provide a unidirectional stream from server to client over plain HTTP. The client opens a connection with the EventSource API; the server keeps the response open and writes events as they occur, using the text/event-stream content type.
const source = new EventSource('/api/events');
source.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
// Update the UI
});
source.addEventListener('error', () => {
// EventSource reconnects automatically — log, don't panic
});SSE’s underrated superpower is built-in reconnection: the browser automatically re-establishes dropped connections and sends the last received event ID (Last-Event-ID header), so the server can resume the stream where it left off. With WebSockets you build all of that yourself.
Use SSE when data flows one way: notification feeds, live scores, progress updates, log streaming, price tickers, news feeds. If the client only ever receives, SSE is simpler, cheaper, and more robust than WebSockets — it traverses proxies and firewalls as ordinary HTTP and needs no special infrastructure.
Limitations to know: SSE is text-only (no binary frames), and over HTTP/1.1 browsers cap connections per domain (six), which bites if you open multiple streams — over HTTP/2 streams multiplex over one connection and the limit disappears in practice.
WebRTC: Peer-to-Peer Audio, Video, and Data
WebRTC (Web Real-Time Communication) is fundamentally different from the other two: it enables direct peer-to-peer connections between browsers — audio, video, and arbitrary data — without routing media through your server.
const peerConnection = new RTCPeerConnection();
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((stream) => {
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
});
// Data channel for low-latency arbitrary data
const dataChannel = peerConnection.createDataChannel('game-state', {
ordered: false,
maxRetransmits: 0 // UDP-like: fast, lossy
});Use WebRTC when you need media (video calls, voice, screen sharing) or ultra-low-latency peer-to-peer data (multiplayer game state, P2P file transfer).
What makes WebRTC harder than it looks:
- Signaling is on you. WebRTC defines how peers talk once connected, but not how they find each other. You build the signaling channel — typically over WebSockets — to exchange session descriptions (SDP offers/answers) and network candidates.
- NAT traversal needs infrastructure. Most peers sit behind NATs. You need STUN servers (cheap — they just discover public addresses) and TURN servers (expensive — they relay media when direct connection fails, which happens for 10–20% of real-world connections). Budget for TURN; without it those users simply can’t connect.
- Security is built in but not complete. All WebRTC media is encrypted by default (SRTP/DTLS) — but your signaling channel, authentication, and room authorization are your responsibility. Always run signaling over HTTPS/WSS.
For most teams, a managed WebRTC platform (LiveKit, Daily, Twilio Video) is the right call unless real-time media is your core product.
Side-by-Side Comparison
| WebSockets | Server-Sent Events | WebRTC | |
|---|---|---|---|
| Direction | Bidirectional | Server → client only | Peer-to-peer, bidirectional |
| Transport | TCP (own protocol) | Plain HTTP | UDP-based (SRTP/SCTP) |
| Data types | Text + binary | Text only | Media + binary data |
| Auto-reconnect | No — build it yourself | Yes, with resume | No — renegotiation needed |
| Proxy/firewall friendliness | Mostly fine (WSS) | Excellent (plain HTTP) | Needs STUN/TURN |
| Server infrastructure | Socket servers + pub/sub backplane | Any HTTP server | Signaling + STUN/TURN |
| Implementation complexity | Moderate | Low | High |
| Best for | Chat, collaboration, dashboards | Feeds, notifications, tickers | Video/audio calls, P2P data |
Choosing — and Combining Them
The decision is simpler than it appears:
- Does the client send real-time data too? No → SSE. Don’t deploy WebSockets for a one-way feed; you’ll build reconnection logic SSE gives you free.
- Yes, two-way? → WebSockets.
- Audio, video, or peer-to-peer? → WebRTC (with WebSockets for signaling).
Real products routinely combine them: a video-conferencing PWA uses WebRTC for media, WebSockets for signaling and chat, and SSE for out-of-session notifications. There is no conflict in using all three — each handles the layer it’s best at. The factors that should drive the split: data direction, payload type (text/binary/media), latency tolerance, infrastructure budget, and how much complexity your team can operate.
Frequently Asked Questions
How do I handle errors and dropped connections with WebSockets?
Listen to close and error events, reconnect with exponential backoff plus jitter (to avoid thundering herds after a server restart), use ping/pong heartbeats to detect silent failures, and queue outbound messages locally while disconnected. Libraries like Socket.IO handle most of this — and fall back to long-polling where WebSockets are blocked.
What are the advantages of SSE over WebSockets? Automatic reconnection with event-ID resume, plain-HTTP transport that works through any proxy, no special server infrastructure, and a far smaller implementation surface. If the data flow is one-directional, SSE wins on operational simplicity.
Is browser compatibility still a concern? Not meaningfully. WebSockets, SSE, and WebRTC are supported by every modern browser, including Safari on iOS (SSE since iOS 5; WebRTC since iOS 11). Legacy-enterprise browser fleets are the only place fallbacks still matter.
How do I implement real-time chat in a PWA? WebSockets for transport; persist messages server-side with sequence IDs; on reconnect, fetch messages since the last seen ID; deliver presence (“user is typing”) as ephemeral events that are never persisted; and use the Push API for notifications when the app is closed — a WebSocket dies with the page, push notifications don’t.
How do I broadcast updates to thousands of clients at once? Publish once to a pub/sub layer (Redis, or a message broker for higher volumes), and let each socket/SSE server fan out to its connected clients. Never loop over clients from application code on a single node — the backplane pattern is what lets you scale horizontally.
How does offline behaviour interact with real-time features? A PWA that’s offline obviously receives nothing — the real question is what happens on reconnect. Combine real-time transport with an offline-first data layer so the app resyncs cleanly; our guide to data synchronization in PWAs covers exactly that problem.
Conclusion
WebSockets, SSE, and WebRTC each solve a different slice of real-time: two-way messaging, one-way push, and peer-to-peer media. Match the technology to the data flow rather than defaulting to the most powerful option — the simplest technology that covers your use case is the one that will still be working smoothly in production a year from now. The hard engineering is rarely the happy path; it’s reconnection, missed-message recovery, and scaling fan-out, so design for those from the start.
Adding real-time features to your product?
We've shipped chat, live dashboards, and collaborative tools on WebSockets, SSE, and WebRTC — and scaled them past the demo stage.
A short architecture conversation before you build can save a rewrite after launch.
Discuss your real-time architectureA technical conversation, not a sales pitch.