HTML Tutorial 0/139 lessons ~6 min read Lesson 59

    HTML SSE

    html sse server-sent events power one-way live updates in production dashboards and log t server-sent events (sse) — new eventsource(url)

    Course progress0%
    Focus
    18 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Server-Sent Events (SSE)new EventSource(url) — opens a long-lived HTTP connection for server→client text streams. Staff engineers choose SSE over WebSockets when traffic is one-way (stock tickers, Netflix live status, sports scores, CI build logs) — simpler protocol, auto-reconnect, HTTP/2 multiplex friendly. Know reverse-proxy buffering (nginx), auth via cookies, and WCAG pause controls for live regions.

    Business problem

    Business pressure: Users expect live dashboards — Uber ETA updates, Robinhood price flashes, GitHub Actions log tail. Polling every 2s wastes server and battery; WebSockets add infra complexity for read-only feeds. SSE fits HTTP caching layers and load balancers better than WS — until bidirectional needed.

    • Conversion: Live inventory counts ("3 left") drive urgency — SSE pushes without full page refresh.
    • Compliance: Financial tick data may need audit log of stream auth — cookie session on same-origin SSE.
    • SEO: Live content not indexed in real-time — SSR static snapshot; SSE enhances logged-in dashboards only.

    Why this feature exists

    Platform motivation: WHATWG EventSource (2006–2011) standardized comet/long-poll replacement with built-in Last-Event-ID reconnection. Simpler than WebSocket for push notifications, news tickers, and ChatGPT-style streaming text (over fetch streams now too).

    • History: Predates fetch ReadableStream streaming; still widely deployed for log tail and metrics.
    • Alternative rejected: Short polling — 10x request overhead on Hacker News scale live threads.
    • Modern role: SSE for one-way; WebSocket for chat/games; fetch stream for LLM tokens — choose by direction and infra.

    Browser internals

    Inside the engine: EventSource creates persistent GET with Accept: text/event-stream. Parser reads SSE format: data: lines, optional event: type, id: for replay, double newline dispatches MessageEvent. On disconnect, browser waits retry ms from server or default and reconnects sending Last-Event-ID header.

    • Parser: UTF-8 text only — binary needs WebSocket or fetch stream.
    • CORS: Cross-origin SSE needs ACAO; cookies withCredentials for auth streams.
    • Limits: Browser max 6 connections per domain HTTP/1.1 — HTTP/2 multiplex helps; still cap EventSource count.
    javascript
    // Server format
    // data: {"price": 142.50}
    // data: partial line
    // data: continued
    const es = new EventSource('/api/ticker');
    es.onmessage = e => updatePrice(JSON.parse(e.data));
    es.addEventListener('halt', () => es.close());

    Rendering workflow

    Rendering path: Each message handler updates DOM — unbatched updates on high-frequency tickers cause layout thrash. Batch with rAF or max 4 updates/sec display — Bloomberg terminal pattern throttles flash. aria-live="polite" on price region — not assertive on every cent change.

    • Critical path: SSE connects after LCP — don't block render on EventSource open.
    • Layout: Fixed-width tabular nums on live prices — prevents CLS from digit width change.
    • Paint: Highlight flash on change — CSS animation 300ms, not permanent background toggle every tick.

    Feature deep dive

    SSE vs WebSocket vs polling: SSE: server→client, text, auto-reconnect, HTTP. WS: bidirectional, binary, manual reconnect. Polling: dumb but firewall-friendly. SSE auth: HttpOnly cookie on same-origin; token in query fragile (logs). Close EventSource on page hide to save battery — Page Visibility API.

    • Proxy: nginx proxy_buffering off for SSE location — or events batch delayed 30s.
    • Heartbeat: Comment lines : ping keep connection alive through proxies.
    • Named events: event: stockUpdate — addEventListener vs onmessage.
    html
    <div id="price" aria-live="polite" aria-atomic="true"></div>
    <div id="conn" role="status">Connecting…</div>
    <script type="module">
    const es = new EventSource('/api/stream/prices');
    es.onopen = () => document.getElementById('conn').textContent = 'Live';
    es.onerror = () => document.getElementById('conn').textContent = 'Reconnecting…';
    document.addEventListener('visibilitychange', () => {
    if (document.hidden) es.close();
    else location.reload(); // or reopen EventSource
    });
    </script>

    Accessibility analysis

    A11y architecture: WCAG 2.2.2 Pause, Stop, Hide — auto-updating stock ticker needs pause button. aria-live polite batches announcements — NVDA floods if assertive on every SSE message. Provide static snapshot table as alternative — don't rely on live stream alone for critical info.

    • Screen readers: Throttle live region updates to meaningful changes (>1% price move).
    • Keyboard: Pause stream button first in toolbar — ESPN live score pattern.
    • WCAG: 2.3.1 Three Flashes — rapid SSE-driven color flash limits.

    SEO impact

    SEO architecture: Live SSE content invisible to crawlers — index static HTML baseline. News sites SSR headline; SSE updates breaking tag client-side — Google sees SSR version. Don't gate indexable article body behind SSE-only load.

    • Crawl: Canonical content in HTML source; SSE is enhancement.
    • Rich results: LiveBlogPosting schema updated server-side for AMP live blogs — not client SSE alone.
    • CWV: Long connection doesn't hurt LCP if deferred post-load.

    Security considerations

    Security boundary: SSE over GET — auth token in URL appears in logs and Referer — use cookies. XSS can open EventSource to attacker's stream — exfiltrate session if misconfigured CORS. Rate-limit connections per user server-side — SSE exhaustion DoS.

    • CSP: connect-src must allow stream endpoint.
    • CSRF: Cookie-auth SSE same-site — SameSite=Lax usually sufficient for GET stream.
    • Injection: If server echoes user content in data: field without encoding — client JSON.parse safe but HTML injection if carelessly rendered.

    Performance impact

    Performance: One EventSource per feed — multiplex events in one stream vs 20 connections. Mobile: close on background tab — iOS kills idle connections anyway. Server: hold thousands of open SSE connections — async Node/Go servers; thread-per-connection Java fails.

    • INP: Handler parses JSON + DOM update 60/sec — throttle to 4Hz display.
    • Memory: Append-only log UI — virtualize list, trim old events.
    • Network: HTTP/2 single connection multiplexes SSE + assets — prefer HTTP/2 origin.

    Real production example

    GitHub Actions log tail / Netflix playback status: SSE or chunked HTTP stream delivers lines; UI appends to virtualized terminal; reconnect on drop with Last-Event-ID resumes from line 4000. nginx location with proxy_buffering off and 86400s read timeout.

    • infra: ALB idle timeout > heartbeat interval — AWS 4000s default kills silent SSE.
    • Fallback: Poll every 5s if EventSource undefined — corporate proxy blocks streaming.
    • Monitoring: Client reconnect count metric — spikes indicate proxy misconfig.
    nginx
    # nginx SSE location
    location /api/stream/ {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 86400s;
    chunked_transfer_encoding off;
    }

    Enterprise usage

    Enterprise: Corporate proxies block long-lived connections — detect onerror loop, degrade to poll. SSO session expiry mid-stream — server sends event: logout; client closes ES and redirects. Financial feeds audit Last-Event-ID replay for compliance.

    • Design system: LiveRegion component with pause, throttle, connection status badge.
    • Ops: Runbook for nginx buffering causing "stale dashboard" tickets.
    • CI: Integration test opens EventSource against test server — assert event sequence.

    Common production failures

    What breaks in prod: nginx proxy_buffering batches 30s of "live" data; ALB idle timeout drops silent streams; 6 SSE connections exhaust HTTP/1.1 limit; aria-live assertive spam; token in query string in CDN logs.

    • Incident: Trading dashboard showed prices 45s delayed — nginx buffering default on.
    • Mobile: Background tab SSE killed — users saw stale data until manual refresh — fixed with visibility reconnect.
    • a11y: Screen reader users couldn't use live scoreboard — no pause, filed complaint.

    Architecture review questions

    • Is nginx/proxy buffering disabled for SSE route?
    • Is there pause control for auto-updating content?
    • Are DOM updates throttled to prevent INP/layout thrash?
    • Is auth via cookie not URL token?
    • Is EventSource closed on page hide / unmount?
    • Is there polling fallback for blocked streams?

    Hands-on project

    Project: Live metric dashboard: EventSource consumer, pause button, rAF-throttled DOM, connection status, nginx config doc, poll fallback.

    • Deliverable: aria-live polite; close on hidden tab; Last-Event-ID resume demo.
    • Verify: Simulate proxy buffering off; axe pause control present.
    • Stretch: Compare fetch ReadableStream vs EventSource for same feed.

    Interview questions

    SSE vs WebSocket — decision criteria?(Advanced)

    SSE: one-way server push, text, auto-reconnect, standard HTTP, simpler LB. WebSocket: bidirectional, binary, gaming/chat/collab. SSE loses if client must send frequent messages over same channel.

    Follow-up: SSE over HTTP/2 benefits?

    Why do SSE streams stall behind nginx?(Advanced)

    proxy_buffering accumulates response until buffer full — disable for text/event-stream location. Also check read timeouts and ALB idle timeout vs heartbeat comment lines.

    Follow-up: Heartbeat implementation?

    Make SSE accessible for a live ticker.(Advanced)

    Pause button; aria-live polite throttled; aria-atomic on summary region; static table fallback; avoid flash; keyboard reachable pause first.

    Follow-up: WCAG 2.2.2 requirements?

    Try it yourself

    Edit the HTML, CSS, or JS panels — the preview updates as you type.

    Try it yourself

    Preview

    Summary

    Server-Sent Events power one-way live updates in production dashboards and log tails — with nginx buffering off, connection lifecycle management, throttled aria-live regions, pause controls, and WebSocket reserved for bidirectional workloads.

    Ready to mark this lesson complete?Track your journey across the entire course.