TanStack / TanStack/router

[Start RSC] SerovalUnsupportedTypeError on RawStream during dev SSR — production unaffected

Open
#7,361 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

information needed
Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

Which project does this relate to?

Start

Describe the bug

@tanstack/react-start/rscloaders that return values fromcreateCompositeComponent(...)` crash during dev-mode SSR dehydration. Production builds are unaffected — same code, same route, dev throws, prod renders.

Server log

Serialization error: SerovalUnsupportedTypeError: The value [object Object] of type "object" cannot be parsed/serialized.
value: RawStream { stream: ReadableStream, hint: 'text' }
at parseObjectPhase2 (seroval/src/core/context/sync-parser.ts:743)
at parseObject (seroval/src/core/context/sync-parser.ts:773)
at parseSOS (seroval/src/core/context/sync-parser.ts:815)
at StreamParsePluginContext.parse (seroval/src/core/context/sync-parser.ts:150)
at Object.stream (router-core/src/ssr/serializer/transformer.ts:193)

Client console

Invariant failed: Expected to find a dehydrated data on window.$\_TSR.router, but we did not. Please file an issue!
at hydrate (ssr-client.js:31)
at hydrateStart (hydrateStart.js:27)

Dehydration aborts mid-write, so window.$_TSR.router never reaches the client and hydrate() throws on load.

Root cause

Vite dev runs each environment (client, ssr, rsc, nitro) through a separate module-runner, so the RawStream class at packages/router-core/src/ssr/serializer/RawStream.ts is loaded once per env with distinct constructor identities.

packages/react-start-rsc/src/serialization.server.ts:183 constructs new RawStream(stream, { hint: 'text' }) in the RSC env. RawStreamSSRPlugin.test() at packages/router-core/src/ssr/serializer/RawStream.ts:323-325 runs in a different env and evaluates value instanceof RawStream — which returns false cross-realm. The plugin doesn't claim the value, the seroval parser falls through, parseObjectPhase2 throws SerovalUnsupportedTypeError at seroval/src/core/context/sync-parser.ts:743. The same instanceof pattern at RawStream.ts:407 in createRawStreamRPCPlugin has the same flaw.

Production Rollup deduplicates everything into a single bundle per target → one RawStream constructor → instanceof works → no crash.

I've validated locally with a bun patch @tanstack/router-core@1.169.2 that swaps instanceof RawStream for a Symbol.for('@tanstack/router.RawStream') brand check (and adds the brand in the constructor). With the patch, both /rsc-test and complex RSC routes start working in dev with zero seroval errors. Production behavior is unchanged. PR with the fix at #.

Complete minimal reproducer

https://github.com/timelessco/reform

Steps to Reproduce the Bug

Branch: main. The route src/routes/rsc-test.tsx is the docs createCompositeComponent example pattern verbatim — see Server Components docs → Composite (slots). It reproduces directly after bun install && bun dev.

1. `git clone https://github.com/timelessco/reform && cd reform`
2. `bun install` (or `pnpm install` / `npm install`)
3. `bun dev`
4. Open `http://localhost:3000/rsc-test`

Observed:

- Server terminal logs `SerovalUnsupportedTypeError: ... value: RawStream { stream: ReadableStream, hint: 'text' }` from `transformer.ts:193`.
- Browser console throws `Invariant failed: Expected to find a dehydrated data on window.$_TSR.router`.
- The page renders the dev-server error overlay instead of the RSC test fixture.

For comparison, the same route on the deployed Vercel build renders correctly:
https://better-forms-mu.vercel.app/rsc-test

The route source — identical to the docs example, ~50 LOC:
https://github.com/timelessco/reform/blob/main/src/routes/rsc-test.tsx
Expected behavior

I expected /rsc-test to render in dev exactly the way it renders in production: the loader return is dehydrated, the RSC payload streams, the client hydrates without error.

Instead, dev-mode SSR aborts dehydration mid-write because Vite's per-environment module-runner causes value instanceof RawStream in RawStreamSSRPlugin.test to return false, so the plugin doesn't claim the value and seroval throws.

Screenshots or Videos

No response

Platform
  • Router / Start Version: @tanstack/react-start 1.167.64, @tanstack/react-router 1.169.2, @tanstack/router-core 1.169.2, @tanstack/router-plugin 1.167.34
  • OS: macOS 25.3.0 (Darwin)
  • Browser: Chrome
  • Browser Version: latest
  • Bundler: vite (with @vitejs/plugin-rsc 0.5.24, @vitejs/plugin-react 5.2.0)
  • Bundler Version: 7.3.1
Additional context

I have patch the package that you give addtional context and for me that actuall resolved the issues, here is the pr link for reference : https://github.com/timelessco/reform/pull/73

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with packages/router-core/src/ssr/serializer/RawStream.ts, especially RawStreamSSRPlugin.test and createRawStreamRPCPlugin, then inspect packages/react-start-rsc/src/serialization.server.ts:183 and router-core/src/ssr/serializer/transformer.ts:193. Run the reform reproducer with bun dev and open /rsc-test. Done means dev SSR dehydrates the loader result, streams the RSC payload, and hydrates without the serialization or missing dehydrated-data errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript, vite
Domain
backend, full-stack
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.