Use polling when occasional updates are sufficient, server-sent events when the server mainly pushes updates to a browser, and WebSockets when frequent communication must flow both ways. The transport does not determine whether messages are durable or authorized. Reconnection, recovery, and hosting constraints often matter more than the first successful live demo.
Start with the user-visible delay
Define how stale the screen may be before it causes a problem. An export status page can usually tolerate a short delay. A collaborative cursor or live interaction may need a different response.
Also separate notifications from commands. A user can submit a change through an ordinary HTTP request and receive subsequent updates through SSE. Bidirectional business behavior does not automatically mean every message must use a bidirectional connection.
Write down the number of active users, the frequency of changes, and the size of each update as estimates to validate. Do not choose a transport from a benchmark that measures only raw messages without your authorization checks and application work.
Polling is often enough
With polling, the client asks the server for current state at intervals. It works with ordinary request-response infrastructure and gives each request a familiar authentication and authorization boundary.
A job status view can poll while the job is active, stop when it completes, and reduce activity when the tab is hidden. Add jitter so a large group of clients does not request updates simultaneously after a page refresh or deployment.
Estimate the baseline request load before implementation. As an illustrative calculation, 600 active clients polling every 15 seconds produce roughly 40 requests per second, even if nothing changes. These numbers are a scenario, not a capacity prediction. Query cost and response size determine whether that traffic is significant.
Use pagination, changed-since queries, or conditional responses where appropriate. Do not repeatedly download a complete activity history to discover that one item was added. The main disadvantage is the compromise between update delay and repeated requests.
SSE fits server-to-browser updates
Server-sent events use a long-lived HTTP response with the text/event-stream content type. The browser's EventSource API receives named or unnamed events and supports reconnection. The HTML standard defines the behavior.
SSE suits export progress, activity notifications, operational dashboards, and text generated incrementally on the server. The client can send commands using separate HTTP requests.
An illustrative event frame looks like this, with a blank line terminating the event:
id: activity-284
event: project.updated
data: {"projectId":"p_91","version":12}
The event ID can support resumption, but it does not create a replay log. The server must retain events or provide another way to recover state when a client reconnects. MDN's SSE guide explains the event fields and stream format.
Check authentication early. Native EventSource does not provide an arbitrary request-header option. Same-origin cookie authentication may fit a browser application; another client or token strategy may require a different implementation. Avoid placing long-lived credentials in URLs that can appear in logs.
WebSockets fit frequent two-way interaction
WebSockets provide a persistent channel for messages in both directions. They are useful for interactive collaboration, presence, and other features that exchange frequent client and server messages. RFC 6455 defines the protocol.
You still need an application protocol. Define message types, validation, authorization, acknowledgments, and error responses. A connection being open does not mean every requested resource is permitted.
Validate the connection origin where appropriate and authenticate the user. Authorize subscriptions to specific tenants or resources. Then consider what should happen if a user's membership is removed while the connection remains open.
Plan for slow consumers. The browser's traditional WebSocket interface does not provide automatic backpressure, as MDN documents. Bound queues and message sizes, coalesce replaceable updates, and disconnect or resynchronize a client that cannot keep up.
Compare the operational responsibilities
| Requirement | Polling | SSE | WebSockets |
|---|---|---|---|
| Traffic direction | Client requests current state | Server pushes; client commands can use HTTP | Both directions on one connection |
| Reconnect behavior | Next request naturally tries again | Browser API supports reconnection | Application or library manages reconnection |
| Missed-update recovery | Fetch current state | Replay or resynchronize explicitly | Replay or resynchronize explicitly |
| Infrastructure | Ordinary HTTP requests | Streaming HTTP support | Persistent socket support |
| Typical starting use | Status and occasional refresh | Progress and event feeds | Frequent interactive messages |
Automatic reconnection is not the same as uninterrupted delivery. A network interruption can leave the client missing updates even after the connection returns.
For a dashboard, reconnecting and fetching a fresh snapshot may be sufficient. For an audit feed, you may need an ordered, durable event stream with a cursor. Choose that data contract separately from the transport.
Verify the hosting path end to end
Test through the actual CDN, reverse proxy, load balancer, and deployment environment. A stream that works on localhost may be buffered or terminated elsewhere. Check idle timeouts, response buffering, maximum execution duration, and connection limits in the host's current documentation.
Next.js can expose streaming responses, but a deployment's execution model determines what remains open. Its backend guide specifically notes restrictions when handlers run as lambda functions. Do not infer persistent WebSocket support from framework support alone.
Use heartbeats when needed to detect or prevent idle connection loss, and clean up subscriptions when clients disconnect. For multiple server instances, decide how an event reaches the instance holding a particular client's connection. A process-local list of subscribers will not distribute updates across the fleet.
Preserve state across reconnects
Make updates versioned or cursor-based when order matters. A client receiving version 14 after version 12 should know whether to fetch fresh state. An old delayed update should not overwrite a newer value.
During initial connection, avoid a gap between fetching the snapshot and subscribing to updates. Use a cursor tied to the snapshot, replay from a known position, or a subscribe-and-resynchronize strategy. The right choice depends on whether the UI needs every event or only the latest state.
The background jobs guide explains durable processing behind progress updates. The tenant architecture guide covers the authorization boundary those updates must retain.
Choose the simplest transport that meets the contract
Start with polling for infrequent status changes. Use SSE when one-way updates make the interface meaningfully better. Introduce WebSockets when the interaction needs a persistent two-way channel and the team can operate it.
Define recovery before polishing animations. A useful realtime feature returns to the correct state after a laptop sleeps, a server deploys, or a user's access changes. That behavior belongs in the pre-launch checklist.