Which project does this relate to?
Start
Describe the bug
In renderRouterToStream.tsx, the onError callback added in #5996 destroys the PassThrough stream on any error, including errors that are caught by React Error Boundaries (recoverable errors).
React's renderToPipeableStream calls onError for all errors — both recoverable (caught by Error Boundary) and unrecoverable. When the error is recoverable:
- Error Boundary catches the error and renders
errorComponent / defaultErrorComponent
onShellReady fires normally — the shell includes the error fallback UI
onError also fires → stream.destroy() kills the already-piping stream
- Response body becomes broken → upstream proxy returns 502 Bad Gateway
This means defaultErrorComponent and route-level errorComponent never render during SSR — users always see a 502 instead of the intended error page.
Your Example Website or App
N/A (reproducible in any TanStack Start app with defaultErrorComponent set)
Steps to Reproduce the Bug or Issue
- Set
defaultErrorComponent in createRouter():
const router = createRouter({
routeTree,
defaultErrorComponent: () => <MyErrorPage />,
})
- Throw an error in any route component:
function RouteComponent() {
throw new Error('test error')
return <div>Hello</div>
}
- Access the page via SSR → 502 Bad Gateway instead of MyErrorPage
Expected behavior
The Error Boundary should catch the error and render defaultErrorComponent during SSR, returning a valid HTML response (with status 500).
Screenshots or Videos
No response
Platform
@tanstack/react-router: 1.167.16 (any version after #5996 merge on 2025-11-29)
@tanstack/react-start: 1.167.16
Additional context
Root Cause
In packages/react-router/src/ssr/renderRouterToStream.tsx:
onError: (error, info) => {
console.error('Error in renderToPipeableStream:', error, info)
// Destroy the passthrough stream on error
if (!reactAppPassthrough.destroyed) {
reactAppPassthrough.destroy(
error instanceof Error ? error : new Error(String(error)),
)
}
},
Proposed Fix
Follow React's recommended pattern:
onShellError(error) {
console.error('SSR Shell Error:', error)
if (!reactAppPassthrough.destroyed)
reactAppPassthrough.destroy(
error instanceof Error ? error : new Error(String(error)),
)
},
onError: (error, info) => {
console.error('Error in renderToPipeableStream:', error, info)
},
onError: logging/flag-setting only, no stream.destroy().
This aligns with React's recommended pattern
where onError is used solely for logging — none of React's official
examples destroy the stream in onError.
onShellError (new): handle unrecoverable shell errors with fallback HTML,
as documented in React's API reference.
Which project does this relate to?
Start
Describe the bug
In
renderRouterToStream.tsx, theonErrorcallback added in #5996 destroys thePassThroughstream on any error, including errors that are caught by React Error Boundaries (recoverable errors).React's
renderToPipeableStreamcallsonErrorfor all errors — both recoverable (caught by Error Boundary) and unrecoverable. When the error is recoverable:errorComponent/defaultErrorComponentonShellReadyfires normally — the shell includes the error fallback UIonErroralso fires →stream.destroy()kills the already-piping streamThis means
defaultErrorComponentand route-levelerrorComponentnever render during SSR — users always see a 502 instead of the intended error page.Your Example Website or App
N/A (reproducible in any TanStack Start app with
defaultErrorComponentset)Steps to Reproduce the Bug or Issue
defaultErrorComponentincreateRouter():Expected behavior
The Error Boundary should catch the error and render defaultErrorComponent during SSR, returning a valid HTML response (with status 500).
Screenshots or Videos
No response
Platform
@tanstack/react-router: 1.167.16 (any version after #5996 merge on 2025-11-29)
@tanstack/react-start: 1.167.16
Additional context
Root Cause
In packages/react-router/src/ssr/renderRouterToStream.tsx:
Proposed Fix
Follow React's recommended pattern:
onError: logging/flag-setting only, nostream.destroy().This aligns with React's recommended pattern
where
onErroris used solely for logging — none of React's officialexamples destroy the stream in
onError.onShellError(new): handle unrecoverable shell errors with fallback HTML,as documented in React's API reference.