Skip to content

fix(router-core): stream post-</body> render output instead of quarantining it - #8026

Open
ryansolid wants to merge 2 commits into
TanStack:mainfrom
ryansolid:fix/stream-transform-post-body-passthrough
Open

fix(router-core): stream post-</body> render output instead of quarantining it#8026
ryansolid wants to merge 2 commits into
TanStack:mainfrom
ryansolid:fix/stream-transform-post-body-passthrough

Conversation

@ryansolid

@ryansolid ryansolid commented Aug 10, 2026

Copy link
Copy Markdown

fix(router-core): stream post-</body> render output instead of quarantining it (out-of-order SSR renderers)

Problem

transformStreamWithRouter assumes the renderer holds <body> open until the render
stream completes — true for React's renderToPipeableStream, where </body></html>
are the final bytes. Solid's renderToStream has the opposite shape (unchanged since
Solid 1.x): it flushes a complete document (…</body></html>) as the shell, then
keeps streaming out-of-order boundary fragments (<template> + $df() swap scripts)
after </body>. Browsers reparent trailing content into <body>; that is the
protocol.

The transform treats everything from the first </body> onward as "tail"
(MergeState.HoldingTailappendTail) and holds it until render and
serialization finish, so its $_TSR script injections land before </body>. Under
Solid's shape that quarantines every boundary fragment until the end of the stream.
Two measured consequences (TanStack Start, Solid 2.0 line, production build; fixture:
three queries under separate boundaries settling at 25 ms / ~717 ms / ~1500 ms):

1. No progressive paint — every boundary is gated on the slowest query on the page.

Server byte stream, before the fix (real-Chrome UA):

t (ms) chunk content
46.5 0 Shell with all three fallbacks, $_TSR.router= blob
62.8 1 shell query resolution script (data on the wire at ~63 ms)
739.4 2 renderOnly query script
1536.8 3 slow query script
1538.0 5 Held tail: </body></html> + all three boundary <template>s + swap scripts

Client side, the shell boundary's data was in the query cache at ~92 ms, but its
markup arrived at ~1538 ms — a ~1.45 s window where the fallback stayed visible with
the data already hydrated. Each boundary's content is delayed to the end of the
entire stream.

2. Hard failure above 64 KB. The tail buffer is capped (MAX_TAIL_CHARS,
64 KB). A streamed boundary rendering ~100 KB of HTML throws
SSR stream tail exceeded maximum buffer server-side, the connection dies
mid-response (net::ERR_INCOMPLETE_CHUNKED_ENCODING), and the user is left with a
permanently stuck loading fallback — the corresponding query hydrated as a pending
promise that never resolves, so the client cannot recover. Any content-heavy streamed
route hits this.

What the transform must guarantee (and why it buffered)

The tail-holding exists for exactly one reason: injected router HTML (the $_TSR
state blob and streamed seroval script chunks) must land inside <body>, i.e.
before the document terminator. With React's shape that is nearly free — the held
tail is just the final </body></html>. The bug is that the hold is keyed on first
sight of </body>
rather than on the terminator itself, so a renderer that keeps
producing after the terminator gets its entire remaining output quarantined.

The transform's other invariant — injected HTML only enters the stream at safe
closing-tag boundaries, never splitting app bytes mid-tag — is independent of the
tail hold and is preserved (see below).

Fix

Hold only the document terminator, and keep streaming everything after it:

  • On seeing </body>, the transform captures </body>[whitespace]</html>
    (case-insensitive, incremental across chunk splits, whitespace bounded at 256
    chars) into pendingTail and emits it last, exactly as before.
  • Everything after the captured terminator re-enters the normal scan-and-inject
    pipeline (new MergeState.PostBody): fragments flush immediately, and router
    script injections interleave with them. Scripts landing after </body> on the
    wire is precisely Solid's own protocol — the parser reparents them into <body>,
    and the relocated terminator means the final document is better-formed than the
    renderer's raw output, not worse.
  • If the bytes after </body> don't look like [whitespace]</html> (unknown
    renderer shape), the transform degrades gracefully: it holds only </body> and
    streams the rest. No path buffers unbounded app output anymore.
  • Injection safety in pass-through: injected HTML is written immediately only when
    the scanner is at a safe boundary (no partial trailing tag held in leftover);
    otherwise it queues and flushes at the next closing-tag boundary, same as before
    </body>. This matters because the bounded-leftover overflow path can flush
    output that ends mid-element (e.g. inside <script> text), where an immediate
    injection would corrupt the stream.

Ordering guarantees, before vs. after

  • $_TSR scripts still always precede the emitted terminator (held tail flushed
    last at tryFinish, after any remaining router HTML). Unchanged.
  • Data-script-before-dependent-markup for streamed queries: previously an accident
    of the quarantine (scripts flushed immediately while all fragments were held).
    Now it holds by arrival order — the seroval chunk for a settled query is injected
    at the settle event, at or before the moment the renderer flushes that boundary's
    fragment, and the e2e capture confirms each data script precedes its fragment on
    the wire. Note the client is also order-tolerant here: fragments swap fallback
    DOM without needing the data, and hydrated pending promises resolve whenever the
    script executes.

The 64 KB cap

MAX_TAIL_CHARS and the SSR stream tail exceeded maximum buffer failure are
removed, not raised: the held tail is now structurally bounded by the terminator
matcher (≤ </body> + 256 chars whitespace + </html>). Remaining buffers, audited:

buffer bound on overflow
pendingTail ~270 chars by construction n/a (matcher gives up and streams through)
leftover (split-tag window) 2 KB rolling older bytes flushed (pre-existing)
pendingRouterHtml 16 MB hard error (pre-existing; router's own scripts, not app output)
pendingWrites (downstream backpressure) 16 MB hard error (pre-existing; consumer stalled)

App render output can no longer terminate the response.

Cross-framework safety

  • React shape is byte-identical. When </body></html> are the final render
    bytes, nothing follows the terminator: the capture completes, pass-through never
    carries app bytes, late serialization scripts still flush before the held
    terminator. A new test asserts exact output equality for a React-shaped sequence,
    and it passes unchanged against the pre-fix transform (verified by stashing the
    fix and re-running: the 4 Solid-shape tests fail on the old code, the React
    byte-identity test passes).
  • Suites: @tanstack/router-core unit suite fully green (105 files, 1575 passed,
    3 pre-existing expected-fails); @tanstack/react-router unit suite fully green;
    @tanstack/solid-router unit suite fully green.
  • One existing test rewritten deliberately: tail overflow errors and runs cleanup
    asserted the old kill-the-connection behavior for >64 KB post-</body> content.
    It is now large post-</body> content streams through instead of erroring, which
    asserts the same input survives, with the rationale in a comment.

Tests added (packages/router-core/tests/transformStreamWithRouter.test.ts)

New describe block out-of-order (post-</body>) streaming, with the measured
timeline as the repro description:

  • Solid-shaped fixture: complete document, then fragments — asserts each fragment is
    released before the stream ends (mid-stream assertion via a continuous
    collector), that an injected data script between fragments flushes immediately and
    precedes the next fragment, and that the document still ends with exactly one
    </body></html>.
  • A >64 KB fragment (the measured hard-failure case) streams through and the
    connection survives to a clean close.
  • Terminator split across chunks (</body></html>) and uppercase
    </BODY>\n</HTML> variants — captured and emitted last, byte-exact output asserted.
  • React-shaped fixture asserting byte-identical output (see above).
  • Injection while a partial fragment tag is pending queues until the next safe
    boundary (the stream-corruption guard).

End-to-end verification (instrumented Start app, Solid 2.0 line)

Same fixture and probes as the original investigation; the fix was applied to the
installed router-core dist as a minimal surgical patch (identical logic) so the
measured delta is exactly this change. Baseline re-measured on the same machine
immediately before.

Server byte stream (fragment arrival):

boundary (query settle) before after
shell (25 ms) 1588 ms (held tail) 83 ms
renderOnly (~717 ms) 1588 ms (held tail) 768 ms
slow (~1500 ms) 1588 ms (held tail) 1561 ms
</body></html> start of held tail final 14-byte chunk

Each data script (chunks 1, 5, 9) precedes its boundary's fragment on the wire.

Client timeline (headless Chrome, real-Chrome UA):

event before after
shell data in cache ~80 ms ~70 ms
shell boundary content rendered 1511.8 ms 77.5 ms
renderOnly boundary content rendered 1514.3 ms 710.6 ms
slow boundary content rendered 1515.1 ms 1508.8 ms
first counter click handled 460 ms 451 ms

The ~1.45 s data-in-cache→content-visible window for the fast boundary is gone; each
boundary now paints at its own data-arrival time instead of the slowest query's.
Client queryFn executions remain 0 (all DOM tokens are the SSR tokens), console
clean apart from a favicon 404.

64 KB case (/big, ~100 KB boundary): before — server threw
SSR stream tail exceeded maximum buffer, connection died at ~98 KB read
(ERR_INCOMPLETE_CHUNKED_ENCODING), fallback stuck forever. After — response
completes cleanly: 394,564 bytes, all 1505 <li> rows, terminates with
</body></html>, zero server errors.

Notes for reviewers

  • findHtmlBoundary's </body>-detection is unchanged; in PostBody a literal
    </body> in content is treated as an ordinary closing-tag boundary rather than a
    second terminator.
  • The fast path (reserveStreamFastPath) is untouched.
  • A widening cast was needed in the read loop (state as MergeState) because the
    state transitions moved into helper closures and TypeScript's control-flow
    narrowing otherwise pins state to its initial value.

Summary by CodeRabbit

  • New Features

    • Improved server-rendered page streaming for faster, more reliable delivery.
    • Supports large and split document terminators, including varied letter casing.
    • Preserves correct ordering for dynamically rendered page fragments.
    • Handles content that appears after the document body closes.
  • Bug Fixes

    • Prevented streaming interruptions when output exceeds previous buffering limits.
    • Improved compatibility with React-generated output.
    • Prevented malformed or incomplete page endings from disrupting streamed content.

…tining it

transformStreamWithRouter assumed the renderer holds <body> open until the
render completes (React's shape). Solid's renderToStream emits a complete
document and then streams out-of-order boundary fragments after </body>;
the transform quarantined all of them in a 64KB-capped tail until render +
serialization finished, defeating progressive paint (fragments gated on the
slowest query on the page) and killing the connection mid-response on
boundaries over 64KB.

Hold only the document terminator (</body>[ws]</html>, captured
incrementally and case-insensitively) and emit it last; everything after it
re-enters the scan-and-inject pipeline so fragments flush immediately and
router scripts interleave at safe closing-tag boundaries. Unknown shapes
degrade to holding just </body> — app output can no longer overflow a tail
buffer, so the 64KB cap and its hard failure are removed. React's shape is
byte-identical: nothing follows its terminator, and late serialization
scripts still land before </body>.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The SSR stream merger replaces fixed tail buffering with incremental document-terminator matching. It streams post-body output, preserves terminator ordering, delays router HTML when partial tags exist, and adds coverage for large, split, uppercase, out-of-order, and React-shaped streams.

Changes

SSR streaming changes

Layer / File(s) Summary
Bounded document-terminator capture
packages/router-core/src/ssr/transformStreamWithRouter.ts, packages/router-core/tests/transformStreamWithRouter.test.ts
The merger adds HoldingTail and PostBody states. It matches split and case-insensitive </body> and </html> sequences with bounded whitespace.
Post-body streaming and injection ordering
packages/router-core/src/ssr/transformStreamWithRouter.ts, packages/router-core/tests/transformStreamWithRouter.test.ts
Post-body content now streams beyond 64 KiB. Router HTML waits for safe scanner boundaries, and the document terminator is emitted once after late fragments. Tests cover large fragments, out-of-order output, partial fragments, and React-shaped output.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested labels: package: router-core

Suggested reviewers: sheraff

Sequence Diagram(s)

sequenceDiagram
  participant AppStream
  participant ReusableScanner
  participant RouterHTML
  participant DocumentTerminator
  AppStream->>ReusableScanner: process app chunks after the document shell
  ReusableScanner->>RouterHTML: flush router HTML at a safe boundary
  ReusableScanner->>DocumentTerminator: retain the bounded document terminator
  DocumentTerminator->>AppStream: emit the terminator after remaining fragments
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes streaming post- render output, which is the primary change in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
packages/router-core/src/ssr/transformStreamWithRouter.ts (2)

788-800: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the vestigial combined alias.

The caller merges leftover into chunkString before calling processScanChunk, so combined is now an exact alias. Use chunkString directly to avoid implying a second buffer.

♻️ Proposed simplification
-      const combined = chunkString
-      if (combined.length > MAX_LEFTOVER_CHARS) {
-        noteBarrierMarker(combined)
-        const flushUpto = combined.length - MAX_LEFTOVER_CHARS
-        const flushed = combined.slice(0, flushUpto)
+      if (chunkString.length > MAX_LEFTOVER_CHARS) {
+        noteBarrierMarker(chunkString)
+        const flushUpto = chunkString.length - MAX_LEFTOVER_CHARS
+        const flushed = chunkString.slice(0, flushUpto)
         writeChunk(flushed)
-        leftover = combined.slice(flushUpto)
+        leftover = chunkString.slice(flushUpto)
       } else {
-        leftover = combined
+        leftover = chunkString
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/router-core/src/ssr/transformStreamWithRouter.ts` around lines 788 -
800, Remove the vestigial combined alias in processScanChunk’s no-closing-tag
branch and use chunkString directly for the length check, noteBarrierMarker
call, slicing, and leftover assignment.

747-802: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add curly braces to the new one-line if bodies. The new code in this file uses one-line if bodies in several places. The coding guidelines require braces for every control statement.

  • packages/router-core/src/ssr/transformStreamWithRouter.ts#L747-L802: add braces to the guard returns on lines 755, 758, 760, 762, 773, and 776, and to if (excess) processScanChunk(excess) on line 763.
  • packages/router-core/src/ssr/transformStreamWithRouter.ts#L147-L172: add braces to the returns on lines 159, 167, and 169.
  • packages/router-core/src/ssr/transformStreamWithRouter.ts#L730-L739: add braces to if (end === -1) return '' on line 733.
  • packages/router-core/src/ssr/transformStreamWithRouter.ts#L949-L964: add braces to if (!chunkString) continue on line 960.

As per coding guidelines: "Always use curly braces for if, else, loops, and similar control statements. Never write one-line bodies like if (foo) x = 1."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/router-core/src/ssr/transformStreamWithRouter.ts` around lines 747 -
802, Add curly braces to every single-line control body in
transformStreamWithRouter.ts: in processScanChunk, wrap the guard returns and
excess recursion; also brace the return guards in the 147-172 section, the end
=== -1 return in the boundary helper, and the !chunkString continue in the
streaming loop. Preserve all existing control flow and behavior.

Source: Coding guidelines

packages/router-core/tests/transformStreamWithRouter.test.ts (2)

163-184: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

collectOutput decodes each chunk independently.

Buffer.from(...).toString('utf8') runs per chunk. A multi-byte UTF-8 sequence split across two chunks would decode as replacement characters. All current test inputs are ASCII, so no test fails today. Use a streaming TextDecoder to make the helper safe for future non-ASCII fixtures.

♻️ Proposed streaming decode
 function collectOutput(s: ReadableStream<Uint8Array>) {
   let done = false
   let received = ''
+  const decoder = new TextDecoder()
   const finished = (async () => {
     const reader = s.getReader()
     while (true) {
       const { done: d, value } = await reader.read()
-      if (d) break
-      received += Buffer.from(
-        value.buffer,
-        value.byteOffset,
-        value.byteLength,
-      ).toString('utf8')
+      if (d) {
+        break
+      }
+      received += decoder.decode(value, { stream: true })
     }
+    received += decoder.decode()
     done = true
   })()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/router-core/tests/transformStreamWithRouter.test.ts` around lines
163 - 184, Update collectOutput to decode the stream with a streaming
TextDecoder across chunk boundaries, passing each Uint8Array chunk through
incremental decoding and flushing the decoder after the read loop. Preserve the
existing finished, isDone, and text behavior while ensuring split multi-byte
UTF-8 sequences decode correctly.

1312-1342: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the unknown-shape terminator path.

These two tests cover the split and uppercase terminators. The -2 branch of matchDocumentTerminator is not covered. Two cases are worth asserting, because they define the emitted byte order:

  • Content between the tags, for example </body><div>x</div></html>.
  • A whitespace run longer than MAX_TERMINATOR_WHITESPACE between </body> and </html>.

Both take the degraded path where only </body> is held, so </body> is emitted after </html>.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/router-core/tests/transformStreamWithRouter.test.ts` around lines
1312 - 1342, Extend the terminator coverage tests around
transformStreamWithRouter with unknown-shape inputs that exercise the
matchDocumentTerminator -2 branch: assert content between </body> and </html> is
emitted with </body> after </html>, and assert the same ordering when the
whitespace run exceeds MAX_TERMINATOR_WHITESPACE. Keep the existing split and
uppercase cases unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/router-core/src/ssr/transformStreamWithRouter.ts`:
- Around line 788-800: Remove the vestigial combined alias in processScanChunk’s
no-closing-tag branch and use chunkString directly for the length check,
noteBarrierMarker call, slicing, and leftover assignment.
- Around line 747-802: Add curly braces to every single-line control body in
transformStreamWithRouter.ts: in processScanChunk, wrap the guard returns and
excess recursion; also brace the return guards in the 147-172 section, the end
=== -1 return in the boundary helper, and the !chunkString continue in the
streaming loop. Preserve all existing control flow and behavior.

In `@packages/router-core/tests/transformStreamWithRouter.test.ts`:
- Around line 163-184: Update collectOutput to decode the stream with a
streaming TextDecoder across chunk boundaries, passing each Uint8Array chunk
through incremental decoding and flushing the decoder after the read loop.
Preserve the existing finished, isDone, and text behavior while ensuring split
multi-byte UTF-8 sequences decode correctly.
- Around line 1312-1342: Extend the terminator coverage tests around
transformStreamWithRouter with unknown-shape inputs that exercise the
matchDocumentTerminator -2 branch: assert content between </body> and </html> is
emitted with </body> after </html>, and assert the same ordering when the
whitespace run exceeds MAX_TERMINATOR_WHITESPACE. Keep the existing split and
uppercase cases unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d2c3e619-1605-4063-b6ce-765084ac681d

📥 Commits

Reviewing files that changed from the base of the PR and between af8dcb8 and 16fa153.

📒 Files selected for processing (2)
  • packages/router-core/src/ssr/transformStreamWithRouter.ts
  • packages/router-core/tests/transformStreamWithRouter.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/router-core/tests/transformStreamWithRouter.test.ts`:
- Around line 1272-1274: Update the test around the resolveQuery script and pl-2
template markers to first assert that both expected strings are present in
output.text(), then compare their offsets to verify the script precedes the
template.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dd7b8fb6-1e83-416e-91f9-844db87a193d

📥 Commits

Reviewing files that changed from the base of the PR and between 16fa153 and c3bd362.

📒 Files selected for processing (1)
  • packages/router-core/tests/transformStreamWithRouter.test.ts

Comment on lines +1272 to +1274
expect(
output.text().indexOf('<script>resolveQuery(2)</script>'),
).toBeLessThan(output.text().indexOf('<template id="pl-2">'))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that the injected script exists before comparing offsets.

indexOf returns -1 when the script is missing. If the template exists, this assertion still passes. Assert the expected markers are present before checking their order.

Proposed fix
+    const text = output.text()
+    expect(text).toContain('<script>resolveQuery(2)</script>')
+    expect(text).toContain('<template id="pl-2">')
     expect(
-      output.text().indexOf('<script>resolveQuery(2)</script>'),
+      text.indexOf('<script>resolveQuery(2)</script>'),
-    ).toBeLessThan(output.text().indexOf('<template id="pl-2">'))
+    ).toBeLessThan(text.indexOf('<template id="pl-2">'))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(
output.text().indexOf('<script>resolveQuery(2)</script>'),
).toBeLessThan(output.text().indexOf('<template id="pl-2">'))
const text = output.text()
expect(text).toContain('<script>resolveQuery(2)</script>')
expect(text).toContain('<template id="pl-2">')
expect(
text.indexOf('<script>resolveQuery(2)</script>'),
).toBeLessThan(text.indexOf('<template id="pl-2">'))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/router-core/tests/transformStreamWithRouter.test.ts` around lines
1272 - 1274, Update the test around the resolveQuery script and pl-2 template
markers to first assert that both expected strings are present in output.text(),
then compare their offsets to verify the script precedes the template.

@nx-cloud

nx-cloud Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit c3bd362

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 10m 45s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2m 6s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-10 20:43:01 UTC

@Sheraff

Sheraff commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

FYI the "Bundle Size", "Labeler", and "PR / Preview" are expected to fail on external forks. "PR / Test" isn't. It looked like an nx infra issue so I started a 2nd run.

Also I'm still trying to stabilize the codspeed benchmarks. So you can fully ignore the memory benchmarks altogether, and take the CPU benchmarks ("Simulation") with a grain of salt.

@pkg-pr-new

pkg-pr-new Bot commented Aug 10, 2026

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@8026

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@8026

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@8026

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@8026

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@8026

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@8026

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@8026

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@8026

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@8026

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@8026

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@8026

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@8026

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@8026

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@8026

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@8026

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@8026

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@8026

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@8026

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@8026

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@8026

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@8026

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@8026

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@8026

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@8026

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@8026

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@8026

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@8026

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@8026

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@8026

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@8026

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@8026

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@8026

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@8026

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@8026

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@8026

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@8026

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@8026

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@8026

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@8026

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@8026

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@8026

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@8026

commit: c3bd362

@codspeed-hq

codspeed-hq Bot commented Aug 10, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 23.68%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
❌ 9 regressed benchmarks
✅ 169 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory mem server error-paths not-found (vue) 329.9 KB 2,297.5 KB -85.64%
Memory mem server error-paths redirect (solid) 288.7 KB 659.4 KB -56.22%
Memory mem server serialization-payload (react) 3.2 MB 5.1 MB -37.44%
Memory mem server request-churn (react) 488.2 KB 602.7 KB -19%
Memory mem server error-paths redirect (react) 203.8 KB 219.6 KB -7.22%
Memory mem server error-paths redirect (vue) 293 KB 309 KB -5.17%
Memory mem server server-fn-churn (vue) 263.2 KB 276 KB -4.64%
Simulation ssr server-fn during document ssr (react) 69.3 ms 71.6 ms -3.21%
Simulation ssr server-fn multipart (solid) 64 ms 66.1 ms -3.13%
Memory mem server error-paths not-found (solid) 661.7 KB 411.6 KB +60.79%
Memory mem client unique-location-churn (solid) 436.9 KB 343.8 KB +27.09%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing ryansolid:fix/stream-transform-post-body-passthrough (c3bd362) with main (af8dcb8)

Open in CodSpeed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants