SSE guide
One-way live streams with eventStream — works on Outer today with keep-alive.
Verdict: works. Server-sent events are plain HTTP streaming —
text/event-stream with no bindings and nothing to declare.
1. Stream from a route
// routes/api/scores.ts
import { defineHandler } from "void";
import { eventStream, getLastEventId } from "void/sse";
export const GET = defineHandler((c) => {
const lastId = getLastEventId(c.req.raw); // null on first connect
return eventStream(
async (stream) => {
await stream.send({
id: "ready-1",
event: "ready",
data: { now: Date.now(), resumedFrom: lastId },
});
await stream.comment("connected");
await stream.closed; // stay open until the client disconnects
},
{ signal: c.req.raw.signal, keepAlive: { intervalMs: 5000, comment: "ping" } },
);
});
// Client
import { connectEventStream } from "void/sse/client";
Gate the route with the ordinary requireAuth(c) when the stream is
private, and support Last-Event-ID resume (getLastEventId(c.req.raw),
null on first connect) by replaying missed events from D1 after lastId
— the server keeps no per-client history for you.
2. Keep-alive is mandatory
Cloudflare’s edge answers 524 past its ~125 s proxy read timeout on an
idle response. The keepAlive comment every few seconds beats it — never
stream silently. Cross-request pub/sub belongs in
queues; two-way traffic belongs in
WebSockets.
Sources
- Void SSE guide: https://void.cloud/guide/sse