Not Everything Needs WebSockets
This might be our most common piece of advice that gets ignored. A client says "we need a real-time dashboard" and the dev team immediately reaches for WebSockets. But when we dig into the requirements, "real-time" usually means "updates every 30 seconds" not "updates within 100ms." Those are very different requirements with very different architectural implications.
We've built real-time dashboards with all three approaches — WebSockets, Server-Sent Events (SSE), and good old polling — and each has a sweet spot that the others can't match.
Polling: Underrated and Often Sufficient
For dashboards that need updates every 15-60 seconds, polling is not just acceptable — it's often the best choice. A simple setInterval that fetches fresh data from a REST endpoint is trivial to implement, trivial to debug, works through every proxy and load balancer, and puts you in complete control of the refresh rate.
We built an internal metrics dashboard for a client that refreshes every 30 seconds. It's polling. The entire real-time infrastructure is one useEffect with a setInterval. It's been running for 14 months without a single incident related to the refresh mechanism. Compare that to the WebSocket-based dashboard we built for another client, where we've had to debug connection drops, reconnection logic, and message ordering issues multiple times.
The cost: polling generates more HTTP requests than push-based approaches. At 100 concurrent users polling every 30 seconds, that's 200 requests/minute. For most backend infrastructures, this is trivial. If you're at 10,000 concurrent users, the math changes, but if you're at that scale, you've probably got bigger infrastructure decisions to make.
SSE: The Sweet Spot for Most Dashboards
Server-Sent Events are our default recommendation for dashboards that need sub-5-second updates. SSE is a one-way channel from server to client — the server pushes events whenever there's new data, and the browser handles connection management, automatic reconnection, and event parsing natively. No library needed on the client side. Just an EventSource object.
We use SSE for our monitoring dashboard product (uptimeMonit) and it handles 5,000+ concurrent connections on a single Node.js instance. The trick: don't keep a database query running per connection. Instead, have one process that queries the database at your desired interval and publishes updates to all connected clients via an in-memory pub/sub (we use Redis for this). Each SSE connection is just a long-lived HTTP response that writes events when Redis publishes them.
SSE works through most proxies and load balancers (it's just HTTP), supports automatic reconnection with last-event-id (the browser handles this!), and is significantly simpler to implement than WebSockets. The limitation: it's server-to-client only. If you need bidirectional communication, SSE won't work.
WebSockets: When You Actually Need Them
WebSockets are the right choice for genuinely interactive real-time features: collaborative editing, live chat, multiplayer games, or trading platforms where latency matters and communication is bidirectional. We used WebSockets for a collaborative document editing feature where multiple users annotate the same document simultaneously. The bidirectional nature is essential — users send their edits and receive others' edits through the same connection.
The operational complexity is real though. WebSocket connections are stateful, which means your load balancer needs sticky sessions or a shared message bus. Connections drop for all sorts of reasons (mobile networks, proxy timeouts, laptop sleep/wake) and your reconnection logic needs to handle state synchronization. We use Socket.io in production because it handles all this for you — the fallback to polling, the reconnection, the room management. Writing raw WebSocket handling is asking for trouble.
Our Decision Framework
Updates every 15+ seconds, read-only? Polling. Updates every 1-15 seconds, server-to-client only? SSE. Sub-second updates or bidirectional communication? WebSockets. And if you're unsure, start with polling. You can always upgrade to SSE or WebSockets later without changing your backend data layer. Going the other direction — downgrading from WebSockets — is harder.