Skip to content

fix(react-router): use onShellError to destroy stream on unrecoverable SSR errors#7083

Open
mixelburg wants to merge 1 commit intoTanStack:mainfrom
mixelburg:fix/renderRouterToStream-onShellError
Open

fix(react-router): use onShellError to destroy stream on unrecoverable SSR errors#7083
mixelburg wants to merge 1 commit intoTanStack:mainfrom
mixelburg:fix/renderRouterToStream-onShellError

Conversation

@mixelburg
Copy link
Copy Markdown

@mixelburg mixelburg commented Mar 31, 2026

Fixes #7078

React's renderToPipeableStream calls onError for all errors — both recoverable (caught by Error Boundaries) and unrecoverable. The previous implementation destroyed the PassThrough stream in onError, which meant:

  1. A route throws an error
  2. React Error Boundary catches it → renders defaultErrorComponent
  3. onShellReady fires normally with the error UI in the shell
  4. onError also fires → stream.destroy() kills the already-piping stream
  5. Response body is broken → upstream proxy returns 502

Fix: Follow React's recommended pattern:

  • onError: logging only, no stream destruction
  • onShellError (new): handle unrecoverable shell render failures with stream.destroy()

onShellError fires only when React cannot produce any HTML at all — the true unrecoverable case.

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling in server-side rendering to better distinguish between shell-level errors and runtime errors, ensuring more accurate error logging and recovery.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 31, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4cd78ad5-356d-4509-b8fc-6fe17f485788

📥 Commits

Reviewing files that changed from the base of the PR and between 5a81726 and 70a6776.

📒 Files selected for processing (1)
  • packages/react-router/src/ssr/renderRouterToStream.tsx

📝 Walkthrough

Walkthrough

The renderRouterToStream function was updated to separate error handling concerns by splitting the unified onError callback into two distinct hooks: onShellError for unrecoverable shell errors (which destroys the passthrough stream) and onError for general errors (which only logs without destroying the stream). This change allows React Error Boundaries to properly render fallback UI during SSR without premature stream termination.

Changes

Cohort / File(s) Summary
Error Handling Refactor
packages/react-router/src/ssr/renderRouterToStream.tsx
Split onError callback into onShellError(error) and onError(error, info). Stream destruction now occurs only in onShellError for unrecoverable shell errors, while onError is reserved for logging recoverable errors without stream destruction.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A stream once fell with hasty might,
Destroying dreams in SSR night,
But now we've split the error dance—
onShellError gets second chance,
While recoverable errors slip through with grace! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: moving stream destruction from onError to onShellError for handling unrecoverable SSR errors.
Linked Issues check ✅ Passed The code changes fully implement the proposed fix from issue #7078: onShellError now destroys the stream for unrecoverable errors, while onError only logs without stream destruction, following React's documented pattern.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue #7078: splitting error handling callbacks in renderToPipeableStream configuration to distinguish recoverable from unrecoverable errors.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

Bundle Size Benchmarks

  • Commit: 5a81726f0a2f
  • Measured at: 2026-03-31T22:28:39.236Z
  • Baseline source: history:796406da66cf
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.48 KiB 0 B (0.00%) 275.76 KiB 75.97 KiB ████▁▁▁▁▁▂▃
react-router.full 90.78 KiB 0 B (0.00%) 286.95 KiB 78.95 KiB ▆▆▆█▁▁▁▁▁▂▂
solid-router.minimal 35.56 KiB 0 B (0.00%) 107.26 KiB 31.94 KiB ████▁▁▁▁▁▂█
solid-router.full 40.03 KiB 0 B (0.00%) 120.79 KiB 35.94 KiB ████▁▁▁▁▁▂▆
vue-router.minimal 53.38 KiB 0 B (0.00%) 153.07 KiB 47.94 KiB ████▁▁▁▁▁▂▄
vue-router.full 58.25 KiB 0 B (0.00%) 168.53 KiB 52.18 KiB ████▁▁▁▁▁▂▄
react-start.minimal 102.01 KiB 0 B (0.00%) 324.00 KiB 88.21 KiB ▅▅▅█▁▂▂▂▂▃▃
react-start.full 105.38 KiB 0 B (0.00%) 334.35 KiB 91.14 KiB ▆▆▆█▁▁▁▂▂▃▃
solid-start.minimal 49.66 KiB 0 B (0.00%) 153.51 KiB 43.84 KiB ▇▇▇▇▁▁▁▂▂▂█
solid-start.full 55.17 KiB 0 B (0.00%) 169.74 KiB 48.46 KiB ▇▇▇▇▁▂▂▂▂▃█

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud bot commented Apr 1, 2026

View your CI Pipeline Execution ↗ for commit 70a6776

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 8m 50s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 42s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-01 22:41:50 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 1, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 70a6776

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 1, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing mixelburg:fix/renderRouterToStream-onShellError (70a6776) with main (796406d)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (5a81726) during the generation of this report, so 796406d was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

onError in renderRouterToStream destroys stream even for recoverable errors, breaking Error Boundary SSR rendering

1 participant