A dashboard that reloads every 30 seconds feels real-time until someone asks why the number on screen doesn't match what just happened in the warehouse. The gap between "refreshes often" and "actually real-time" is entirely about transport and aggregation: how updates get pushed to the browser, and how much math happens before they arrive. I've built both kinds, and the honest answer is that most dashboards don't need sub-second updates — they need updates that are never stale by more than the business cares about, delivered without hammering your database on every poll.
Polling, SSE, or WebSockets
Polling is the version everyone starts with and the version that's fine for a surprising number of cases: a client sets an interval, hits an endpoint, gets JSON back. It's stateless, it works through any proxy, and it's trivial to reason about. It falls over when you need sub-5-second latency across many clients, because now you're multiplying client count by poll frequency against your backend. Server-Sent Events (SSE) solve the "push updates without polling" problem with a plain HTTP connection the browser keeps open and the server writes to — one direction only, server to client, which is exactly what most dashboards need since users aren't sending data back over that channel. WebSockets are the right call when you genuinely need bidirectional communication — a user toggling a filter that changes what the server streams next — but they cost you more infrastructure: sticky sessions or a shared pub/sub layer if you scale past one server process, and reconnect logic you have to write yourself. If your dashboard is read-only, reach for SSE before WebSockets; it's less to operate.
const stream = new EventSource('/api/metrics/stream');
stream.addEventListener('metric', (event) => {
const { key, value, ts } = JSON.parse(event.data);
updateChart(key, value, ts);
});
stream.onerror = () => {
// EventSource auto-reconnects, but back off if the server
// is actually down instead of hammering it every second.
console.warn('SSE connection dropped, browser will retry');
};
Aggregate before it leaves the server
The most common real-time dashboard mistake is streaming raw events to the browser and doing the rollup in JavaScript. It works in the demo with ten events a minute and falls apart at production volume, because now every open tab is independently summing the same numbers, and a slow client can't keep up with the firehose. Do the aggregation server-side — a rolling window job that maintains current counts, sums, and rates in Redis or an in-memory store, updated as events arrive — and push only the aggregate, not the raw stream. The browser's job is to render a number that's already correct, not to be a second analytics engine.
If ten events land in the same second, don't push ten WebSocket messages. Batch updates on a short interval (100-500ms) server-side and send one message per window. The dashboard doesn't need to animate every individual event — it needs the current state to be correct within a bound the user won't notice.
What breaks under load
The failure mode that actually matters isn't "the chart is slow" — it's connection exhaustion. Each open SSE or WebSocket connection holds a file descriptor and, depending on your server, a thread or event-loop slot. A dashboard with a thousand concurrent viewers is a thousand long-lived connections, and if your load balancer or reverse proxy has a default idle timeout of 60 seconds, connections silently drop and reconnect in a loop that looks like the feature is flaky when it's actually a config mismatch. Set explicit keep-alive intervals on the server side (a comment or heartbeat event every 15-30 seconds) and make sure your proxy's timeout is longer than that interval, not shorter.
If the server restarts and every client reconnects at the same instant, you get a thundering herd against the same backend that just came back up. Add jitter to client-side reconnect delays — a random 0-2 second offset — so a deploy doesn't turn into a self-inflicted spike.
Picking a refresh floor, not a ceiling
Teams over-invest in shaving milliseconds off latency for dashboards nobody is staring at pixel-by-pixel. Before building a streaming pipeline, ask what staleness the business actually can't tolerate — a support queue depth dashboard might need 2-second updates because agents route work off it live, while a weekly revenue dashboard is fine refreshing every few minutes. Match the transport to that number. Polling every 10 seconds is simpler to operate than a WebSocket fleet, and if it meets the actual requirement, it's the better engineering decision, not a compromise.
Wrapping up
Real-time dashboards live or die on two decisions made early: pick the lightest transport that satisfies your actual latency requirement (polling, then SSE, then WebSockets — in that order of preference), and push pre-aggregated numbers instead of raw events so the browser never has to do the math the server should have done. The failures that show up in production — dropped connections, reconnect storms, a client-side aggregation bug that only appears under real traffic — are almost always about not planning for the connection count, not about the chart library.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.