There's a moment in every telemetry project where someone asks for a dashboard that updates in real time. You nod. You open a blank architecture diagram. And then you stare at it for a while, because "real time" covers a lot of ground.

A satellite position that updates once a second. A fleet of temperature sensors firing every 200ms. A live ops console where a missed event means someone gets paged. These are not the same problem dressed up in different nouns. The transport, the buffering, and the processing layer all change depending on what you're actually trying to do.

This is how I think about the stack when I'm building something that has to feel live in a browser.

Start with what "real time" means for your data

Before you pick a protocol, get specific about three things.

Latency budget. Does the UI need to reflect a change within 50ms, 500ms, or five seconds? A satellite tracker and a stock ticker have different answers.

Fan-out. Is one producer feeding one browser, or one producer feeding ten thousand dashboards?

Durability. If a client disconnects for thirty seconds, do you need to replay what they missed, or is the latest value enough?

Those three questions eliminate most of the wrong choices before you've written any code.

The browser side: how the web actually pushes data

Browsers weren't built for push. They were built for request/response. Everything that feels live today is a workaround for that, and the workarounds have different tradeoffs.

WebSockets

WebSockets are the default answer when you need bidirectional, low-latency communication. One TCP connection, full-duplex, binary or text frames. Socket.IO, ws, uWebSockets — pick your wrapper, the shape is the same.

This is what I reach for for telemetry dashboards. The server pushes updates as they arrive. The client can send commands back — change a filter, subscribe to a different sensor group, acknowledge an alert. That bidirectionality matters more than people expect.

The catch is operational. WebSockets are stateful. Every open connection is memory and a file descriptor on your server. At a few hundred concurrent users you're fine. At tens of thousands you need a load balancer that understands sticky sessions, or a shared pub/sub layer so any server instance can reach any client. More on that in a minute.

Server-Sent Events (SSE)

SSE is the underrated option. One-directional — server to client — over a plain HTTP connection. No special protocol upgrade. Automatic reconnection built into the browser API. Works through most corporate proxies that murder WebSocket handshakes.

If your telemetry flow is strictly producer → consumer with no client commands, SSE is often the simpler choice. I've used it for live log tailing, status feeds, and anything where the client just needs to listen. Less infrastructure, fewer edge cases, and EventSource is a few lines of JavaScript.

The limitation is real: no client-to-server channel without opening a separate HTTP request. If you need both directions, you're back to WebSockets or a hybrid.

Long polling and friends

Long polling still shows up in legacy systems and in environments where nothing else gets through the firewall. The client holds a request open until the server has data, then immediately opens another one. It works. It's also chatty and wasteful compared to a persistent connection.

I mention it because you'll encounter it, not because I'd choose it for a greenfield telemetry project.

The middleware layer: Redis, MQTT, and message brokers

Your browser connection is the last mile. Before data gets there, it usually passes through something that handles fan-out, buffering, and decoupling producers from consumers.

Redis Pub/Sub and Redis Streams

Redis Pub/Sub is fast and simple. A producer publishes to a channel, every subscriber gets the message. Perfect for live dashboards where you only care about the current state — latest temperature, current position, active alert count.

The problem is durability. Pub/Sub doesn't persist messages. If no one's subscribed when the event fires, it's gone. For telemetry where the latest value wins, that's often fine. For anything where you need a replay log, it's not.

Redis Streams fixes that. Messages persist, consumers read at their own pace, and you get consumer groups for parallel processing. I've used Streams as a lightweight event log when Kafka would be overkill — a few thousand events per second, a handful of consumers, no need for a dedicated cluster.

The mental model: Pub/Sub for live fan-out, Streams when you also need history.

MQTT

MQTT was built for constrained devices and unreliable networks. Small payloads, QoS levels, last-will messages when a device drops offline. If your telemetry sources are IoT sensors, embedded hardware, or anything running on a battery in a field, MQTT is often the right ingest protocol.

The broker — Mosquitto, EMQX, HiveMQ — handles the hard parts: device authentication, topic routing, retained messages for slow subscribers. Your web app doesn't connect to the sensors directly. It connects to the broker, or more commonly to a bridge that translates MQTT topics into something your web stack understands.

I've seen this pattern a lot: sensors → MQTT broker → stream processor → WebSocket server → browser. The MQTT layer stays close to the devices. The web layer stays close to the users. Clean separation.

Kafka and proper stream processing

When event volume gets serious — millions of events per hour, multiple downstream consumers with different requirements, the need to reprocess historical data — you move into Kafka territory.

Kafka is a distributed commit log. Producers append events. Consumers read at their own offset. You can add a new consumer tomorrow and replay from the beginning if you need to. That replay capability is the whole point.

Stream processors — Kafka Streams, Flink, ksqlDB — sit on top and turn raw events into derived state. Rolling averages, anomaly detection, windowed aggregations. The kind of thing you don't want to do in your WebSocket handler because it'll fall over the first time someone opens two browser tabs.

For a side project or a small fleet dashboard, Kafka is probably too much. For a platform where telemetry is the product, it's often exactly right. Know which side of that line you're on before you commit.

Putting it together: a pattern that actually works

Here's the architecture I keep coming back to for web telemetry at moderate scale.

Ingest. Devices or services publish to MQTT, HTTP, or directly to a message bus depending on the source.

Buffer. Redis Streams or Kafka holds the event log. This decouples producers from consumers and gives you replay.

Process. A worker reads the stream, applies whatever logic you need — filtering, aggregation, alerting — and writes derived state to Redis or Postgres.

Fan-out. A WebSocket or SSE server subscribes to Redis Pub/Sub channels (or reads from the stream) and pushes updates to connected clients.

Display. The browser renders the latest state. Reconnect logic handles dropped connections. If you need history on reconnect, fetch a snapshot from the API and then resume the live stream.

Each layer does one job. The ingest layer doesn't know about browsers. The WebSocket server doesn't run aggregations. When something breaks, you know where to look.

What I'd actually pick for different scenarios

A dashboard for a handful of internal users, updates every few seconds. SSE or WebSockets directly from your API server. Skip the message broker until you feel pain.

A fleet tracker with hundreds of moving assets. WebSockets with Redis Pub/Sub for fan-out across server instances. Postgres or Redis for latest-state snapshots.

IoT sensors in the field with intermittent connectivity. MQTT ingest, Redis Streams or Kafka for buffering, WebSocket fan-out to the web UI.

High-volume analytics with multiple downstream consumers. Kafka from the start. Stream processing for aggregations. A separate read path for the live dashboard so heavy queries don't share infrastructure with the hot path.

None of these are permanent. The right architecture at a thousand events per minute is wrong at a million. That's fine. Build for what you have now, keep the boundaries clean, and swap the middle layers when the numbers demand it.

The part nobody talks about on blog posts

Backpressure. What happens when your producers outrun your consumers? When a browser tab falls behind and has a thousand messages queued?

You need a strategy. Drop old messages and keep only the latest per key. Sample or aggregate upstream. Disconnect slow clients before they take down the server. "Real time" doesn't mean "deliver every single event to every client regardless of cost." It means the client sees fresh data within your latency budget. Sometimes that requires throwing data away on purpose.

Also: observability. Instrument the stream. Measure lag between ingest and display. Alert when consumer groups fall behind. The telemetry system needs its own telemetry. I've debugged too many "the dashboard feels slow" reports that turned out to be a Redis Pub/Sub subscriber that silently stopped reconnecting.

Closing thought

There's no single best protocol for real-time web telemetry. WebSockets for interactive dashboards. SSE when you just need a feed. MQTT when your sources live at the edge. Redis when you need fast fan-out. Kafka when you need a durable log and serious throughput.

The interesting work isn't picking the trendiest tool. It's matching the transport to your latency budget, your fan-out shape, and your durability requirements — then keeping those layers separate enough that you can change one without rewriting everything else.

That's the exploration. The implementation is where it gets fun.