Skip to main content

Server-Sent Events (SSE): Real-Time Push Alternative

Server-Sent Events (SSE) is a simpler, browser-native alternative to WebSockets for pushing server data to clients. SSE uses a single HTTP connection that stays open and streams text events. Unlike WebSockets, SSE is unidirectional (server to client only) and built on HTTP, making it easier to deploy behind proxies and firewalls. Use SSE for notifications, live feeds, and dashboards where the client never needs to send updates to the server (Crater Labs, 2026).

What Is SSE and How Does It Work?

Server-Sent Events, standardized in WHATWG HTML Living Standard, is an HTTP-based protocol for pushing data from server to client. The browser opens a persistent HTTP connection using the EventSource API. The server responds with Content-Type: text/event-stream and sends events as simple text messages. The browser automatically reconnects if the connection drops—no manual reconnection logic needed.

Example SSE stream from the server (raw HTTP response):

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

data: {"message": "Hello, client!"}

event: price-update
data: {"ticker": "AAPL", "price": 215.42}

event: price-update
data: {"ticker": "MSFT", "price": 341.15}

: This is a comment, ignored by the browser

Each message is a data: line followed by content. You can optionally name events with event:, set a unique ID with id:, or include retry timing with retry:. The browser parses these automatically.

SSE in React: EventSource API

The browser's native EventSource API handles SSE connections:

import { useEffect, useState } from 'react';

export function LivePricesComponent() {
const [prices, setPrices] = useState({});
const [connected, setConnected] = useState(false);

useEffect(() => {
// Open an SSE connection to /api/prices/stream
const eventSource = new EventSource('/api/prices/stream');

eventSource.addEventListener('price-update', (event) => {
const data = JSON.parse(event.data);
setPrices((prev) => ({
...prev,
[data.ticker]: data.price,
}));
});

eventSource.addEventListener('open', () => {
setConnected(true);
});

eventSource.addEventListener('error', (error) => {
if (error.readyState === EventSource.CLOSED) {
setConnected(false);
}
});

return () => {
eventSource.close();
};
}, []);

return (
`<div>`
`<p>`Status: {connected ? 'Connected' : 'Disconnected'}`</p>`
`<ul>`
{Object.entries(prices).map(([ticker, price]) => (
`<li key={ticker}>`{ticker}: ${price}`</li>`
))}
`</ul>`
`</div>`
);
}

Notice how the browser handles reconnection silently. If the server closes the connection, the browser waits (default 3 seconds) and reconnects automatically.

SSE vs WebSocket: Feature Comparison

FeatureSSEWebSocket
DirectionServer to clientBidirectional
ProtocolHTTP-basedTCP upgrade
Firewall-friendlyYes (standard HTTP)Sometimes requires setup
Auto-reconnectYes, built-inManual implementation needed
MultiplexingSingle connection per originYes, true multiplexing
Latency50–200 ms10–50 ms
Best forFeeds, dashboards, notificationsChat, collaboration, games
Browser supportAll modern browsersAll modern browsers

SSE shines in simplicity. Auto-reconnect is built-in, no need for fallback logic. You use HTTP, so no special firewall rules. The trade-off: only the server sends; the client cannot push messages without a separate REST API call.

When to Use SSE Over WebSockets

Choose SSE for:

  • Notification feeds: Social updates, email alerts, activity logs—always server to client.
  • Live dashboards: Stock tickers, weather, analytics—read-only streaming.
  • Live logs: Real-time server logs, deploy status, event streams for monitoring.
  • One-way broadcasts: CEO announcement, system alerts affecting all users.
  • Simplicity: Your app has no need for bidirectional communication; SSE is simpler to implement and debug.
  • Behind proxies: SSE works reliably in environments where WebSocket setup is blocked.

Avoid SSE if:

  • The client must send updates to the server in real-time (use WebSocket or a hybrid approach).
  • You need multiplexing (single SSE connection is per-origin; WebSocket allows multiple logical channels).
  • You need true low-latency gaming (WebSocket beats SSE in latency-sensitive scenarios).

SSE with Authentication and Custom Headers

One gotcha: EventSource cannot send custom headers like Authorization out of the box. Work around this by:

Option 1: Use a token in the URL query parameter:

const eventSource = new EventSource('/api/feed?token=eyJhbGc...');

Option 2: Use a proxy endpoint that injects headers:

// Server-side proxy (e.g., with Express)
app.get('/api/feed-proxy', (req, res) => {
const token = req.headers.authorization;
const upstreamUrl = `https://upstream-api.example.com/stream`;

https.get(upstreamUrl, {
headers: { Authorization: token },
}, (stream) => {
res.setHeader('Content-Type', 'text/event-stream');
stream.pipe(res);
});
});

Option 2 is more secure since tokens never hit the client-side code.

Handling SSE Disconnections and Backoff

While EventSource auto-reconnects, you may want custom backoff. The server can send a retry: directive:

retry: 5000
data: {"message": "reconnect in 5 seconds if disconnected"}

Or manually implement backoff:

let retries = 0;
const maxRetries = 5;

eventSource.addEventListener('error', () => {
eventSource.close();
const delayMs = Math.min(1000 * Math.pow(2, retries), 30000);
retries++;

if (retries <= maxRetries) {
setTimeout(() => {
eventSource = new EventSource('/api/feed');
retries = 0; // Reset on success
}, delayMs);
} else {
console.error('Max reconnection attempts exceeded');
}
});

Key Takeaways

  • SSE is HTTP-based, unidirectional, and auto-reconnecting—ideal for feeds, dashboards, and notifications.
  • SSE requires only an EventSource on the client; the browser handles reconnection automatically.
  • Use SSE when the server always pushes and the client never sends; use WebSocket for bidirectional, low-latency apps.
  • SSE works reliably behind corporate proxies and is simpler to deploy than WebSocket.

Frequently Asked Questions

Does SSE support binary data?

No, SSE sends only text. If you need to stream binary (images, audio), use WebSocket or HTTP range requests with a separate REST endpoint.

Can I send multiple SSE streams to one client?

Yes, but each stream needs a separate EventSource connection. For many streams, WebSocket with multiplexing is more efficient.

What if the server never sends data—does SSE waste bandwidth?

No. An idle SSE connection sends no heartbeat data by default. Some servers send periodic colons (:) to keep the connection alive against proxy timeouts, but it's minimal overhead.

How do I test SSE locally?

Use curl to stream events: curl http://localhost:3000/api/feed. For a React component, log all events: eventSource.addEventListener('message', (e) => console.log(e.data)).

Further Reading