Server-Sent Events
The API route returns a ReadableStream body with content-type: text/event-stream; the page opens an EventSource. The bext API wrapper drains the stream
before returning, so all events arrive in one batch when the
producer closes — EventSource still fires each data: as a separate onmessage. True
incremental flushing is a planned follow-up (needs an EvalMode::ApiHandlerStreaming).
Live stream
Source
// src/app/api/sse/route.ts — emits text/event-stream
export async function GET(): Promise<Response> {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ tick: 0, at: Date.now() })}\n\n`));
for (let i = 1; i <= 8; i++) {
await new Promise(r => setTimeout(r, 750));
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ tick: i, at: Date.now() })}\n\n`));
}
controller.close();
},
});
return new Response(stream, {
headers: { "content-type": "text/event-stream" },
});
}
// page.tsx — client opens an EventSource
const es = new EventSource("/api/sse");
es.onmessage = (e) => render(JSON.parse(e.data));