[pull] canary from vercel:canary#1127
Merged
Merged
Conversation
…#94783) The Cache Components streaming dev render streams the response while filling caches in the background, but when those caches are already warm it should behave like a production render, delivering the cached content as part of the shell rather than behind a Suspense fallback. The render previously held the stream back until the shell stage had been buffered before releasing it. That was enough for the SSR render, whose Fizz pass consumes the buffered shell server-side, but not for the browser. The browser decodes the response as it arrives over the network in multiple chunks, and across those chunks the Flight client can surface the tree to React before a boundary's children row has been processed, so React reads the still-pending children, suspends, and commits the boundary's fallback, then swaps in the content once that row is processed. Holding the stream server-side doesn't fix this, because the bytes still reach the browser in multiple network chunks; the race is in how the client processes them. The flash showed up on warm navigations to a runtime-prefetch route. This change releases the stream to the browser live and exposes a dev-only `_revealAfter` promise on the RSC payload to close that race. Fully buffering the response on the client, so the Flight client processes it in a single chunk, would avoid the race, but a streaming navigation can't be buffered without blocking it on all the dynamic content that streams in after the shell. Instead, the server resolves the promise once the shell-stage content (the static shell, or the runtime-prefetchable shell for runtime-prefetch routes) has been flushed to the stream, or earlier on a cache miss, since a cold render can't be prod-representative anyway and we would rather let everything stream as fast as possible. A client navigation gates revealing the response on it, deferring resolving the response's deferred RSCs until it settles. Because the promise's resolution row follows the children's row in the RSC payload, the children's row has already been processed by the time the client unblocks, so the fallback no longer appears. The SSR render keeps the original hold. The `_revealAfter` reveal gate is a client-side mechanism and doesn't apply to the HTML render, which consumes the same payload to produce the initial HTML and would otherwise stream a boundary's fallback before its content arrived. For that consumer the render holds the stream until the shell-stage content has flushed, so the HTML reflects the prerendered shell rather than a premature fallback. A first navigation to a not-yet-known route needs separate handling. Such a navigation has no prior cache entry, so the server returns the new subtree's content inline in the navigation response rather than as a deferred RSC, which means the deferred-RSC gate doesn't cover it. `navigateToUnknownRoute` now awaits `_revealAfter` (decoded from that response) before building the navigation tree, so the inline content is decoded by the time React reads it, the same gate the known-route path applies to its deferred RSCs. On the server, `streamStagedRenderInDev` resolves `revealAfter` at the shell boundary (or earlier on a cache miss) in one task and advances into the dynamic stage in the next, so the resolution row flushes ahead of the dynamic chunks and the client unblocks as soon as the shell is ready rather than only while the dynamic content streams. The shell-boundary stage, previously `streamReleaseStage`, is renamed `revealAfterStage` to match the new mechanism. A regression test in `cache-components-dev-streaming` hard-reloads the home page and then navigates to the runtime-prefetch route many times, exercising both the unknown-route inline-seed first navigation and the known-route deferred-RSC path, and asserts that the private cache's fallback never enters the DOM. The SSR path remains covered by the existing `ppr-root-param-fallback` test, which checks that a warm initial render places the cached content in the shell rather than its `Suspense` fallback.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )