DEV Community

Cover image for You're Probably Reaching for WebSockets When You Don't Need Them
turboline-ai
turboline-ai

Posted on

You're Probably Reaching for WebSockets When You Don't Need Them

There's a pattern that shows up constantly in developer discussions: someone needs to push data from a server to a browser, and the answer that comes back is almost always "use WebSockets." It makes sense on the surface. WebSockets are well-known, well-documented, and feel like the right tool for anything involving real-time data.

But "real-time" covers a lot of ground, and for a significant portion of those use cases, WebSockets bring complexity that the problem simply doesn't warrant. Feed updates, progress bars, live dashboards, notification systems — most of these share one thing in common: the data only ever flows one direction. The server talks. The browser listens.

That's a different problem than what WebSockets are designed to solve, and there's a native browser API that handles it with far less ceremony.

What Server-Sent Events Actually Do

Server-Sent Events (SSE) keeps a single HTTP connection open and lets the server push data through it whenever something changes. The browser side uses the native EventSource API. No polling loops, no WebSocket upgrade handshake, no third-party library required.

The wire format is plain text with a Content-Type: text/event-stream header. Each event looks like this:

data: {"temperature": 72.4, "unit": "F"}

data: {"temperature": 73.1, "unit": "F"}
Enter fullscreen mode Exit fullscreen mode

And on the client, consuming it is just a few lines:

const source = new EventSource('/stream/temperature');

source.addEventListener('message', (event) => {
  const reading = JSON.parse(event.data);
  updateDashboard(reading);
});

source.addEventListener('error', () => {
  console.warn('Stream interrupted, browser will reconnect automatically');
});
Enter fullscreen mode Exit fullscreen mode

That last comment is worth pausing on. The EventSource API handles reconnection automatically. If the connection drops, the browser retries without you writing any logic for it. You can even send a Last-Event-ID header from the server so the client resumes from where it left off.

Why Developers Overlook It

SSE has been part of the HTML specification for over a decade, and browser support across Chrome, Firefox, Safari, and Edge has been solid since 2020. It is not experimental. It is not niche. It just quietly does its job while WebSockets get most of the attention.

Part of the overlooking comes from how "real-time streaming" gets framed in tutorials and documentation. The examples tend to jump straight to chat applications, collaborative editing, or multiplayer games, which are legitimate two-way communication problems. WebSockets make sense there. But those examples set a mental model that equates "real-time" with "bidirectional," and that mental model sticks.

The other part is that SSE runs on standard HTTP, which can feel anticlimactic. Developers sometimes assume that streaming data requires something more exotic. It does not. An HTTP response that never closes, with the right content type, is all the protocol you need.

Where SSE Actually Fits

The honest answer is that SSE covers a wider range of use cases than most developers give it credit for.

Build status progress pushed to a browser tab while a CI job runs. Live metrics on a monitoring dashboard. Notifications delivered the moment they're triggered on the server. Log tailing in a dev tool. Stock prices, sports scores, sensor readings. Any situation where the server has new information and the browser should receive it as soon as it's available, without the browser asking.

What SSE does not fit is true bidirectional communication at high frequency. If the client needs to send data back frequently and latency matters on both sides, WebSockets earn their complexity. If you're building a video game or a collaborative drawing tool, use WebSockets. But if you're building a dashboard that refreshes when data changes, you're almost certainly adding unnecessary infrastructure by reaching for them.

The Infrastructure Argument

SSE works over HTTP/1.1 and HTTP/2. Your existing load balancer understands it. Your reverse proxy handles it. You do not need sticky sessions configured in a special way, a separate WebSocket upgrade path, or a different port. The connection is just a long-lived HTTP response.

HTTP/2 makes this even more appealing because a single TCP connection can multiplex many SSE streams simultaneously, which solves one of the few real drawbacks SSE had under HTTP/1.1.

On the server side, the implementation is minimal. Express, FastAPI, Go's net/http, Rails ActionController::Live, Spring's SseEmitter — the pattern is the same everywhere. Open a response, set the content type, write events as they become available, flush, keep the connection alive.

The Concrete Takeaway

Before you spin up a WebSocket server, add a connection broker, and figure out how to scale stateful connections across instances, ask whether your data flow is actually bidirectional. If the answer is no, SSE gives you streaming data over plain HTTP with native browser support, automatic reconnection, and zero extra protocols to manage. It won't solve every real-time problem, but it solves more of them than most developers think.

Top comments (0)