Skip to content
Outer
Esc
↑↓navigate↵open⌘Jpreview
On this page

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

Was this page helpful?