The Seductive Simplicity of SSE
At first glance, Server-Sent Events are a breath of fresh air for developers. The goal is simple: a server needs to send updates to a client—like a new notification, a stock price change, or a sports score—without
the client constantly asking for it. Instead of the heavy, two-way communication of WebSockets, SSE uses a standard, long-lived HTTP connection where the server can push text-based messages whenever it needs to. On the client side, implementing it is trivial thanks to the browser's native `EventSource` API. You point it at a URL, and it just works, even automatically handling reconnections if the connection drops. This simplicity makes it a perfect fit for dashboards, live activity feeds, and progress bars. It feels clean, lightweight, and built right into the web platform.
The Invisible Wall of Proxies and Firewalls
The first major headache appears where you can't see it: in the infrastructure between your server and the user. SSE relies on a single HTTP response that never closes. However, many corporate firewalls, load balancers, and reverse proxies (like Nginx by default) are configured for short, finite requests. They might see a long-lived, open connection as an error or a resource leak. Some will buffer the entire stream of events, waiting for the connection to close before sending anything to the client, completely defeating the purpose of real-time updates. Others might just kill the connection after a short timeout. Troubleshooting these issues can be a nightmare because the problem isn't in your code, but in an invisible middleman that is fundamentally hostile to SSE's design.
Automatic Reconnection Isn't a Silver Bullet
One of SSE's most advertised features is automatic reconnection. If the network blips and the connection is lost, the browser will try to reconnect on its own. While helpful, this feature creates a new set of problems. The browser handles the attempt to reconnect, but you, the developer, are responsible for what happens to the data. Neither SSE nor WebSockets can guarantee message delivery. If the connection drops, messages sent from the server during that outage are lost forever unless you build a sophisticated system to track them. By sending a unique `id` with each event, you can tell the browser where it left off, but your server now needs a way to store and replay those missed events. This turns your simple, stateless endpoint into a stateful service that needs to manage message history for every connected client.
The Scalability and Connection Limit Trap
SSE’s model of one open connection per client seems manageable at first, but it creates significant scaling challenges. Every active user holds a persistent connection, consuming server memory and resources. Historically, this was made worse by a browser limitation under HTTP/1.1, which only allowed about six concurrent connections per domain. If a user opened multiple tabs, they could quickly exhaust that limit. While modern servers using HTTP/2 can handle many more simultaneous streams (often defaulting to 100), the fundamental issue remains: you have thousands of stateful, open connections to manage. This complicates load balancing, as an update for a specific user must be routed to the exact server holding their connection, often requiring sticky sessions or a message bus like Redis to coordinate events across all instances.
It's a One-Way Street (and That's Not Always Enough)
By design, SSE is a unidirectional protocol: the server talks, and the client listens. This is a feature, not a bug, and it's what makes SSE simpler than the full-duplex (two-way) communication of WebSockets. However, developers often underestimate how quickly a seemingly one-way feature needs a response. For example, streaming an AI response to a user works great until you want to add a 'cancel' button. With SSE, the client can't send a message back on the same channel; it must make a completely separate HTTP request. This hybrid approach can become clunky and introduces more latency compared to WebSockets, where both sides can communicate freely over the same connection. Furthermore, SSE only supports UTF-8 text, meaning any binary data must be encoded (e.g., to Base64), adding overhead.








