SSR Streaming

Send HTML to the browser progressively as coroutines resolve — like React's renderToPipeableStream, but in PHP. Three APIs, same result: the browser paints content incrementally.

📤

Generator yield

Return a \Generator from your handler. Each yield $html flushes immediately. No API changes needed.

🔁

stream() callback

Get a $write(string) closure. Headers flush before callback runs. Fine-grained control.

📡

sse()

Server-Sent Events. Get an $emit($data, $event, $id) closure. JS EventSource compatible.

Generator SSR — parallel coroutine fetches
$app->route('/stream/ssr', function() {
    $start = microtime(true);
    return (function() use ($start) {
        // 1. Shell sent to browser immediately
        yield '<html><body><h1>Page</h1>';

        // 2. Parallel fetch via coroutines
        $ch = new Channel(2);
        go(fn() => [$ch->push(fetchUsers()), co::sleep(1)]);
        go(fn() => [$ch->push(fetchPosts()), co::sleep(2)]);

        // 3. Stream each section as it resolves
        yield '<div id="users">' . $ch->pop() . '</div>';  // arrives at ~1s
        yield '<div id="posts">' . $ch->pop() . '</div>';  // arrives at ~2s
        yield '</body></html>';
    })();
    // Total: ~2s (parallel), not 3s (sequential)
});

Live streaming demos

GET/stream/ssr — Generator yield

Opens a streaming connection. Watch sections appear one by one (1s, then 2s):

Click Run to start…
SSE/stream/events — Server-Sent Events

10 events, 1 second apart. EventSource reconnects automatically on drop.

Click Connect to start…
SSE — $response->sse()
$app->route('/stream/events', function($response) {
    $response->sse(function($emit) {
        $emit(json_encode(['status' => 'connected']), 'open');
        for ($i = 1; $i <= 10; $i++) {
            co::sleep(1);
            $emit(json_encode(['tick' => $i, 'time' => date('H:i:s')]), 'tick', (string)$i);
        }
        $emit(json_encode(['done' => true]), 'done');
    });
});

// Browser:
// const es = new EventSource('/stream/events');
// es.addEventListener('tick', e => console.log(JSON.parse(e.data)));