IAnswerable

Server-Sent Events: The Underrated Option

21 Jul 2026

If your “real-time” feature only needs the server to push updates to the client — no messages flowing back — Server-Sent Events do the job with a fraction of the code a WebSocket setup needs, and they run over plain HTTP.

What it actually is

SSE is a long-lived HTTP response that the server keeps writing to, formatted as a simple text stream. No handshake protocol, no separate port, no library to add to your server — it’s just an HTTP response with the right content type that never closes.

app.get('/events', (req, res) => {
  res.set({
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
  })
  res.flushHeaders()

  const send = (data) => res.write(`data: ${JSON.stringify(data)}\n\n`)

  const interval = setInterval(() => send({ time: Date.now() }), 5000)

  req.on('close', () => clearInterval(interval))
})

The client side is built into every browser — no library needed:

const events = new EventSource('/events')
events.onmessage = (e) => {
  const data = JSON.parse(e.data)
  render(data)
}

That’s the whole feature. Compare that to a WebSocket setup with a separate server, a connection lifecycle, and a message protocol you design yourself.

Reconnection is built in

This is the part people don’t expect: EventSource reconnects automatically if the connection drops, with no code from you. The browser retries after a delay it controls (you can hint it with a retry: field in the stream).

To resume where the client left off instead of replaying everything, tag each message with an ID:

id: 482
data: {"type":"order_shipped","orderId":123}

The browser sends that ID back as a Last-Event-ID header on reconnect, and your server can use it to send only what the client missed:

const lastId = req.headers['last-event-id']
const missed = await getEventsSince(lastId)
missed.forEach(send)

WebSockets give you none of this. You’d write the reconnect loop, the backoff, and the resync logic yourself.

Where it breaks

When to use it

Notifications, live dashboards, status updates, streaming an LLM response token by token — anything where the server has news and the client just needs to hear it. It’s less code than WebSockets, it reconnects itself, and it runs through your existing HTTP infrastructure without new ops work. Reach for WebSockets only once you have a real bidirectional requirement — SSE plus a normal POST endpoint for the occasional client-to-server message covers more cases than people assume.