-
Notifications
You must be signed in to change notification settings - Fork 478
RFC: Support progressive HTML rendering via chunked transfer encoding #1517
Description
(Note: I'm not an English Native, so this post was redacted with the help of Claude, hence the AI style if you wonder )
When a server sends a response using chunked transfer encoding (StreamedResponse, flush()), browsers natively render HTML as chunks arrive — users see content before the full response completes.
Turbo defeats this by calling response.text() in FetchResponse, which buffers the entire response before the DOM swap. The browser receives chunks progressively, but Turbo holds them until the connection closes.
This matters for pages with expensive backend work (DB queries, API calls). With native navigation, the shell (nav, sidebar, CSS) renders in ~50ms while queries run. With Turbo, users stare at the old page until everything is done.
Proposal
Add an opt-in streaming visit mode. When the server includes a header like X-Turbo-Stream-Body: true, Turbo reads the response as a ReadableStream and progressively appends decoded chunks into a target container instead of doing a full body swap at the end.
A chunk delimiter (e.g. <!-- turbo-chunk -->) emitted by the server between flush() calls would let Turbo inject only complete HTML fragments, avoiding broken DOM from partial network reads.
Example server-side (PHP/Symfony)
return new StreamedResponse(function () {
echo $this->renderShell(); // head, nav, sidebar, open <main>
echo '<!-- turbo-chunk -->';
flush();
echo $this->renderTrackList(); // heavy DB query
echo '<!-- turbo-chunk -->';
flush();
echo $this->renderFooter(); // close </main>, scripts
flush();
});Workaround we use today
We intercept turbo:before-visit, cancel it, and use XMLHttpRequest with onprogress to read chunks and insertAdjacentHTML them into <main>. It works users see content streaming in progressively while the audio player stays alive but it bypasses Turbo's history, cache, and progress bar. We'd love to not maintain this hack.