Skip to content
why mercure / sse

Most apps don’t need WebSockets.

Server-Sent Events runs over plain HTTP, reconnects itself, traverses firewalls cleanly, and ships in every browser. Mercure builds what SSE deliberately leaves out: authenticated publishing, topic filtering, and replay.

1 lineto subscribe — new EventSource(url)
0 KBof client SDK to ship to browsers
100%of modern browsers — native EventSource
HTTP/2multiplexed on one TCP socket
side by side

WebSocket: both ways always.
SSE: just outbound.

Two different shapes of network, two different operational profiles.

WebSocketws:// — full-duplex

Persistent socket. Custom framing protocol.
Both sides can send anytime — even when nobody needs to.

Mercure / SSEhttps:// — server-push

Long-lived HTTP response, text/event-stream.
Browser auto-reconnects and replays via Last-Event-ID.

verdict

Which one for your stack?

recommended for most apps

Pick Mercure / SSE when…

  • Your app pushes server-driven updates (notifications, dashboards, LLM tokens, agent progress, REST/GraphQL streaming).
  • You want bidirectional messaging without paying for a stateful socket on every client.
  • You operate behind corporate proxies, mobile networks, or CDNs you don’t fully control.
  • You want every browser, HTTP client, and CDN to just work with no client SDK.
niche cases

Pick raw WebSockets when…

  • You stream high-frequency binary payloads (raw audio/video, multiplayer position deltas).
  • Round-trip latency at the network level is a hard product requirement.
  • You run on infrastructure you fully own and your traffic profile justifies the operational cost.
  • You are willing to maintain a WebSocket SDK across web, iOS, Android, and server-to-server.
01 · the problem

Most real-time UI is 95% one-way.

Status badges, progress bars, live order books, streaming LLM tokens, push notifications, live CRUD lists. Every one of these is server-to-client. Outbound, from the user’s perspective, is just an HTTP request: a POST, a PUT, a DELETE. You’re paying for a full-duplex socket and only using half of it.

02 · the insight

HTTP can stream. It always could.

The browser-native EventSource API opens a long-lived HTTP response and reads events as they arrive. Plain text over the wire, no framing protocol, no SDK to ship.

  • One direction over plain HTTP. The server holds the response open and writes events as they happen.
  • EventSource ships in every browser. No SDK on the web.
  • Auto-reconnect is part of the standard. The browser reconnects on its own and replays via Last-Event-ID.
  • Compatible with HTTP/2 multiplexing. One TCP connection carries many streams. The old “6 connections per origin” objection went away with HTTP/2, deployed everywhere since 2018.
03 · the solution

Mercure adds what SSE leaves out.

SSE deliberately doesn’t ship publish authorisation, topic filtering, fan-out, or persistence. That’s exactly what Mercure adds — a single Go binary in front of your app, speaking the same plain HTTP your backend already speaks. JWT-authorised publishing, URI-template topic matchers, Redis/PostgreSQL transports for HA, history replay via Last-Event-ID. Run the OSS hub yourself, or use the EU-hosted Cloud.

And “bidirectional” stays cheap. Subscribe is a GET /.well-known/mercure the hub holds open; publish is an authenticated POST to the same endpoint. A browser whose JWT allows it can POST too — so you get WebSocket-style two-way messaging without a stateful socket per client.

04 · side by side

Operational properties, line by line.

Neutral comparison. Both protocols work; they have different operational profiles.

PropertyMercure / SSERaw WebSockets
Connection typeHTTP request kept open, multiplexed over HTTP/2+Stateful TCP connection upgraded from HTTP
Browser APIEventSource, native, no SDKWebSocket, native, but you ship an SDK to layer auth, replay, and topic logic on top
Reconnect with replayStandard. Last-Event-ID is in the HTML spec.Application-defined; your SDK implements it
AuthenticationStandard HTTP auth, JWT, cookies; works with every proxy and load balancerPer-message or query-string token; no standard
Through proxies & corporate networksHTTP requests survive most middleboxesSome middleboxes buffer or drop the upgrade handshake
Mobile battery impactLower; no socket keep-alive on the clientHigher; sockets need keep-alive pings
CompressionStandard HTTP compression (gzip, br, zstd via Caddy)Per-frame permessage-deflate; not always on
Horizontal scalingStateless front, shared transport (Redis, PostgreSQL) at the hub layerSticky routing or shared pub/sub layer; same problem, more state
Binary payloadsVia Base64 (≈33% overhead)Native binary frames
start in five minutes

Start streaming in five minutes.