ID

Home·Writings

SSE for rare alerts: when WebSockets are the wrong interrupt

Site-wide ‘special report is live’ toasts are one-way and infrequent. Why Server-Sent Events (or even short polling) beat idle WebSocket farms for that product shape, using Northlight Press as the example.

Northlight Press sometimes drops a special report - a long-form exclusive that should interrupt the quiet homepage for a few minutes: a compact toast, “Special report is live,” link into the piece, dismissible per reader.

Someone reaches for WebSockets so “everyone sees it instantly.”

That is usually the wrong first tool.

What the product actually is

A special-report toast is not chat. It is not presence. It is not a collaborative cursor.

It is:

  • Rare (a handful of times a week, not a firehose)
  • One-way (server → client; the reader’s dismiss only hides locally)
  • Small payload ({ id, title, href } or null)
  • Tolerant of a few seconds of lag

If you keep a WebSocket open for every idle reader “just in case,” you are paying connection and sticky-session cost for an event that mostly does not happen.

The shape that fits

Server: when an editor publishes a post flagged as a special drop, upsert the active alert, clear any previous one, bust cache, emit a domain event (special.activated / special.cleared). Optional later: FCM for mobile.

Client discovery (pick one):

ApproachFit
Short poll GET /alerts/special every 15–30s + on tab focusFine MVP. Cache-friendly. Easy to reason about.
SSE (text/event-stream)Better when you want sub-second push without bidirectional sockets.
WebSocketSave for studio chat, live comments, presence - two-way, high chatter.

Dismiss stays client-only (localStorage by alert id). Closing the toast for you must not clear the drop for everyone else.

Why SSE wins over WebSockets here

1. Direction matches the problem.
SSE is HTTP streaming from server to client. A special-report toast is exactly that. WebSockets give you a full duplex pipe you will mostly ignore.

2. Operational simplicity.
SSE rides normal HTTP/1.1 or HTTP/2. Fewer sticky-load-balancer stories than long-lived WS fleets. Reconnect is a boring EventSource concern, not a custom heartbeat protocol on day one.

3. Idle cost.
A news site has many tabs open and quiet. Polling with a short TTL cache is cheap. SSE is a single long request per tab, still lighter than framing “real-time platform” around rare alerts. WebSockets shine when the pipe is busy.

4. You can graduate.
Ship poll → swap the client to EventSource on /alerts/special/stream when you care about latency. The domain model (one active special alert, public DTO, cache key) does not change.

A minimal SSE contract

Public:

  • GET /alerts/special → current alert or null (first paint + clients that do not want a stream)
  • GET /alerts/special/stream → SSE; send an event when activated/cleared (and optionally a heartbeat comment so proxies do not kill the idle connection)

Admin still uses the normal authenticated publish path. The stream is a read model fan-out, not a second write path.

On multi-instance APIs, fan-out the domain event over Redis pub/sub (or your bus) so every Node process can push to its connected SSE clients. Single box: Nest EventEmitter is enough.

When WebSockets are right

Use them when the session is conversational or continuous: live studio chat, collaborative editing, multiplayer presence, trading ticks. If your product sentence includes “users talk to each other in real time,” sockets earn their keep.

A special-report toast’s product sentence is: “tell everyone something important just went live.” That is a notification edge, not a conversation.

Takeaway

Match the transport to the frequency and direction of the signal.

For Northlight’s special-report interrupt: event-driven backend + poll or SSE. Keep WebSockets for surfaces that need a busy, bidirectional pipe. The wrong abstraction is not “real-time.” It is “always-on duplex for a rare one-way alert.”