fix: eliminate SSR streaming race between onShellReady and onAllReady#323
Open
Uttkarsh-Srivastava wants to merge 1 commit into
Open
fix: eliminate SSR streaming race between onShellReady and onAllReady#323Uttkarsh-Srivastava wants to merge 1 commit into
Uttkarsh-Srivastava wants to merge 1 commit into
Conversation
|
DeputyDev will no longer review pull requests automatically.To request a review, simply comment #review on your pull request—this will trigger an on-demand review whenever you need it. |
renderToPipeableStream's pipe(res) in onShellReady and the handler's
own res.write()/res.end() in onAllReady were two independent
completion paths racing to close the same response. Whichever lost
was silently dropped (no error listener on res), truncating every
response: missing closing tags on react-dom 19.x, or lost deferred
asset scripts on 18.x, with Suspense fallbacks stuck permanently on
some documents.
Route React's output through a Transform ("tail") instead. React
auto-ends tail exactly like it auto-ended res before, but tail's
flush() hook is guaranteed to run once, appending the deferred asset
tags, before a plain tail.pipe(res) is allowed to end res. There is
now exactly one caller of res.end().
Rebased onto main post the Vite Release merge (#284), which replaced
the old webpack packages/catalyst-core/src/server/renderer/handler.js
with this handler.jsx (same defect, new location) and added safe-area
handling (cleanupSafeArea) that this fix preserves.
Fixes #320
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Uttkarsh-Srivastava
force-pushed
the
fix/ssr-streaming-race-webpack
branch
from
July 22, 2026 10:07
61d7ff9 to
20639e0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
renderToPipeableStream'spipe(res)inonShellReadyand the handler's ownres.write(firstFoldCss/firstFoldJS)/res.end()inonAllReady(packages/catalyst-core/src/server/renderer/handler.js) are two independent completion paths racing to close the same response. Whichever loses is silently dropped (noerrorlistener onres), truncating every SSR response and permanently sticking async<Suspense>boundaries on their fallback in some documents.Transform("tail"). React auto-endstailexactly like it auto-endedresbefore, buttail'sflush()hook is guaranteed to run exactly once — appendingfirstFoldCss/firstFoldJS— before a plaintail.pipe(res)is allowed to endres. Exactly one caller ofres.end()now.resimmediately as React writes it; only the trailing asset tags were ever deferred to the end, same as before.Fixes #320
Visual walkthrough of the race and the fix: https://claude.ai/code/artifact/79a44698-079d-47f0-b72e-911a352f05e4
Test plan
</html>is present and Suspense content resolvesfirstFoldCss/firstFoldJSstill injected correctly for both bot and non-bot requests