Decision
Server sent events against WebSockets, and why I usually pick SSE
WebSockets are the default answer to the word realtime, and most realtime requirements are one directional. The server has something to say, the browser needs to hear it, and nothing goes back the other way.
The short answer
Use server sent events when the traffic is server to browser only, which covers notifications, live status, progress and dashboards. Use WebSockets when the browser genuinely needs to push at high frequency, meaning collaborative editing, presence, chat and games.
Verdict
SSE by default. WebSockets when the browser is a frequent sender rather than an occasional one.
What realtime usually means
Look closely at most realtime requirements and the traffic is one directional.
A notification appears. A status changes from processing to complete. A progress bar advances. A dashboard number updates. A long running job finishes.
In every one of those the server has something to say and the browser listens. The browser sends nothing back except ordinary requests when somebody clicks something, and an ordinary request is a POST.
WebSockets solve a harder problem: both sides pushing frequently, at low latency. That is a real problem and it is not this one.
What SSE gives you for free
It is plain HTTP. That single fact carries most of the argument.
Your existing authentication works, because it is a normal request with normal cookies or headers. Your proxies and load balancers already handle it. Compression works. Your logging and tracing see it. There is no second protocol to secure, monitor and reason about.
Reconnection is built into the browser. When the connection drops, the browser retries on its own, and it sends back the last event identifier it saw so the server can resume rather than restart. With WebSockets you write that yourself, and everybody writes it slightly wrong the first time.
And it degrades sensibly. On a bad connection SSE is a stalled HTTP response that recovers. A WebSocket that cannot establish needs a fallback path, which is more code that runs rarely and is therefore the least tested code you own.
When WebSockets are genuinely right
When the browser is a frequent sender, not an occasional one.
Collaborative editing, where every keystroke is an event. Presence, where clients heartbeat continuously. Chat with typing indicators. Anything with a game loop. Binary streaming.
In those cases the overhead of a request per message is the actual constraint, and a persistent bidirectional connection is the correct tool. Reaching for it when the browser sends something twice a minute is paying that price for nothing.
The failure I keep seeing
Not choosing WebSockets. Choosing them and then opening one per feature.
Notifications open a socket. The dashboard opens another. A progress indicator opens a third. Each was built by somebody solving their own problem sensibly, and the result is four persistent connections per tab, four reconnection strategies that behave differently, and a server holding four times the connections it needs.
Whichever transport you pick, one connection per application, with events multiplexed over it by type. That is a decision to make on the first realtime feature, because retrofitting it means touching every consumer.
The honest downsides of SSE
Text only, so binary needs encoding. One directional by definition, so anything the client sends is a separate request. And on HTTP/1.1 the six connections per origin limit is real, which is another reason the one stream rule matters.
None of those bite for notifications, status, progress or dashboards, which is most of what gets called realtime.
The verdict
SSE by default, because it is less infrastructure for the same outcome and it inherits everything you already run. WebSockets when the browser genuinely needs to push at high frequency. And one connection either way.
Questions people actually ask
- Is SSE not an older, weaker technology?
- It is older and it is narrower, which is different from weaker. For one directional streaming it does the job over plain HTTP, with automatic reconnection built into the browser, and it inherits your existing authentication, proxies and compression rather than needing all of them again.
- What about the connection limit?
- Over HTTP/2 it is not the constraint people remember from HTTP/1.1, where six connections per origin was a genuine problem. On HTTP/1.1 it still matters, and the answer is one stream shared across the whole application rather than one per component.
- How does the client send anything with SSE?
- With an ordinary request. That is the point. Occasional writes do not need a persistent bidirectional socket, they need a POST.