A WebSocket is a persistent, bidirectional TCP connection between client and server. Once the handshake completes, both sides can push messages to each other at any time. This is genuinely useful when:
- The client sends frequent messages to the server (chat, collaborative editing, multiplayer games) - Low latency in both directions is critical (trading platforms, live auctions) - You need binary data streaming
WebSockets require a stateful server — you can't run them behind a standard HTTP load balancer without sticky sessions or a shared pub/sub layer (Redis, etc.). They don't automatically reconnect. Error handling is more complex. Proxies and firewalls occasionally interfere with them.
Server-Sent Events use a regular HTTP connection that stays open. The server pushes events down to the client as newline-delimited text. The client cannot send messages back over the same connection — for that it uses a normal HTTP POST.
SSE gives you: - Automatic reconnection built into the browser spec - Works through any standard HTTP proxy or CDN - Works with HTTP/2 multiplexing (multiple SSE streams over one connection) - Simple text protocol — trivial to debug with curl - Native browser EventSource API with no library required
The limitation: server-to-client only. But think about how many "real-time" features actually work this way.
Ask yourself one question: does the client need to send real-time data to the server, or does it just need to receive it?
Notification systems — SSE. Live dashboards — SSE. Activity feeds — SSE. Order status updates — SSE. Live sports scores — SSE. Comment sections that update live — SSE.
Chat — WebSocket (client sends messages). Collaborative document editing — WebSocket. Multiplayer games — WebSocket. Voice/video signalling — WebSocket.
The majority of applications that claim to need WebSockets actually only need the server to push data to the client. They use WebSockets because developers are more familiar with them, not because they're the right tool.
A Node.js HTTP server handling SSE connections is stateless per-request in the same way any long-poll endpoint is. You can scale it horizontally behind a standard load balancer with no sticky sessions. Events are published via Redis pub/sub and any server instance can deliver them to any connected client.
A WebSocket server is stateful. The client is connected to a specific server instance. To scale horizontally you need sticky sessions on the load balancer (which breaks availability guarantees) or a socket coordination layer. Redis with socket.io-adapter is the common solution, but it's an operational dependency that SSE doesn't require.
For a team running on Vercel, Cloudflare Workers, or any serverless platform: WebSockets are significantly more painful to operate. SSE works natively.
In the projects I build: SSE for anything where the server is the source of truth and the client is a consumer. This covers the vast majority of real-time features in business applications.
WebSockets for chat features, collaborative editing (alongside CRDTs or OT), and anything where sub-100ms round-trip in both directions matters.
If you're in doubt, start with SSE. It's simpler, easier to debug, cheaper to operate, and you can always migrate to WebSockets for specific features if you find you genuinely need bidirectional real-time. The reverse migration is harder.
Ready to take action?