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));