Choose the channel

Start with deliberate polling

Use conditional HTTP polling when updates are infrequent and a short delay is acceptable.

Polling is not a failure state. When incidents change a few times per hour and a fifteen second delay is fine, a measured poll beats a permanent connection you still have to debug.

We start the status board here on purpose. You get a baseline for request count, bytes, and worst-case staleness before adding SSE or WebSockets.

A snapshot endpoint with validators

The board asks for the full incident list on a timer. The server returns JSON plus an entity tag so unchanged responses stay cheap:

// GET /api/incidents
// Response headers:
// ETag: "v3"
// Body: { incidents: [...], generatedAt: "2026-09-08T14:00:00.000Z" }

On the client, pass the last tag back:

let etag = null

async function pollIncidents() {
  const headers = etag ? { 'If-None-Match': etag } : {}
  const res = await fetch('/api/incidents', { headers })

  if (res.status === 304) {
    console.log('unchanged')
    return
  }

  etag = res.headers.get('ETag')
  const body = await res.json()
  console.log('applied', body.incidents.length, 'incidents')
}

Run pollIncidents() every fifteen seconds. When nothing changed, the server answers 304 Not Modified and the body stays empty. That is the signal you want to see in DevTools.

Add backoff and visibility

Naive polling hammers the server when the laptop lid closes or the network flaps. Tie the interval to page visibility:

let timer = null

function startPolling() {
  clearInterval(timer)
  timer = setInterval(pollIncidents, document.hidden ? 60000 : 15000)
}

document.addEventListener('visibilitychange', startPolling)
startPolling()

When the tab is hidden, stretch the interval. When the user comes back, poll once immediately, then resume the normal cadence.

Measure before you upgrade

Log three numbers for two minutes: request count, bytes transferred, and the age of the newest incident on screen. If all three look acceptable, polling may be the final design, not a placeholder.

My advice is to keep this route even after you add a live stream. Polling is your recovery path when the stream dies.

Try this on your own project: implement conditional GET with ETag, create two incidents during the test window, and write down the worst staleness you measured.

Lesson completed