fix(server): serialize chained async memo values resolved after a nested Loading boundary commits#2798
Open
brenelz wants to merge 1 commit into
Conversation
…ted Loading boundary commits
A chained async memo reached through a synchronous derived memo resolves only
after its dependency, so inside a nested <Loading> boundary it serializes after
the surrounding boundary has already flushed/committed. The late serialization
landed in a buffer that never flushed again, dropping the value. On the client
the memo re-ran its compute and orphaned the server-streamed fragment
("unclaimed server-rendered node"). This is the shape produced when route
content is nested in a root layout's boundary (e.g. TanStack Start).
Once a boundary has flushed, later serializations now write through to the
parent context instead of buffering into a buffer that will never flush again.
Adds regression coverage to the "SSR Streaming — Chained Async" suite for
single, nested, deeply nested, and renderToStringAsync cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 088f97e The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Merging this PR will improve performance by 36.58%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | merge |
331.5 µs | 222.8 µs | +48.77% |
| ⚡ | omit |
215.7 µs | 172 µs | +25.4% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing brenelz:fix/nested-loading-boundary-chained-memo-serialization (088f97e) with next (a4ca10b)
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.
Problem
A chained async memo reached through a synchronous derived memo drops its serialized value when it lives inside a nested
<Loading>boundary. On the client the memo then re-runs its compute and orphans the server-streamed fragment:Minimal repro (the shape TanStack Start produces — route content nested in the root layout's boundary):
The serialized stream contains
a's[1]but notb's["item 1"].Root cause
bdepends ona, so it resolves aftera— and in the nested case that is after the surrounding boundary has already flushed and committed itsserializeBuffer.b's lateserializecall landed in a buffer that is never flushed again, so the value was silently dropped. (asurvives because it resolves before the commit.) Single-boundary works only by timing luck.Fix
Once a boundary has flushed, later serializations write through to the parent context instead of buffering into a buffer that will never flush again. Safe because buffer resets only happen during retry discovery, which is always before the first flush.
Tests
Added to the existing
describe("SSR Streaming — Chained Async")suite: single boundary, nested boundary, deeply nested boundaries, andrenderToStringAsyncnested. The three nested cases fail without the fix and pass with it; single passes either way. Full server (91), hydration (18), and client (298) suites pass.🤖 Generated with Claude Code