Which project does this relate to?
Start
Describe the bug
When a route configured with ssr: false encounters an error during its beforeLoad or loader phase (e.g., an auth middleware throwing UNAUTHENTICATED), the error-handling path in the client environment attempts to import react-dom/server.browser.js via renderRouterToString.js. This module does not provide a default export in the browser environment, resulting in:
Uncaught (in promise) SyntaxError: The requested module
'/node_modules/.pnpm/react-dom@19.2.6_react@19.2.6/node_modules/react-dom/server.browser.js?v=b87af0c2'
does not provide an export named 'default' (at renderRouterToString.js?v=b87af0c2:1:8)
This only occurs in development — production builds are unaffected.
Root cause analysis
The import chain appears to be:
- Route has
ssr: false + a loader that calls a createServerFn protected by auth middleware
- Unauthenticated user navigates to the route → middleware throws
- TanStack Router's error boundary rendering path pulls in
renderRouterToString
renderRouterToString.js does import ReactDOMServer from 'react-dom/server'
- In the client/browser environment, Vite resolves this to
react-dom/server.browser.js which is an ESM module that does not have a default export — only named exports (renderToString, renderToStaticMarkup, etc.)
- The import fails with the SyntaxError above
Since the route explicitly opts out of SSR (ssr: false), the error boundary should never need server-side rendering utilities in the client graph.
Complete minimal reproducer
Project: hendripermana/permoney (branch feat/m1-5-stability-gate)
Minimal route shape that triggers the issue:
import { createFileRoute } from "@tanstack/react-router"
import { createServerFn } from "@tanstack/react-start"
const protectedFn = createServerFn({ method: "GET" }).handler(async () => {
// Simulates auth middleware throwing
throw new Error("UNAUTHENTICATED")
})
export const Route = createFileRoute("/protected")({
ssr: false,
loader: async () => {
await protectedFn()
return null
},
component: () => <div>Protected content</div>,
})
Steps to Reproduce the Bug
- Create a TanStack Start app with a route that has
ssr: false
- Add a
loader that calls a createServerFn which throws an error (e.g., auth check fails)
- Run
vite dev (or equivalent)
- Navigate to the route while unauthenticated
- Observe the console error about
react-dom/server.browser.js not providing a default export
Expected behavior
For ssr: false routes, the error boundary rendering path should not import react-dom/server in the client environment. The error should be handled entirely client-side (e.g., rendering the route's errorComponent without server rendering utilities).
Workaround
Add a beforeLoad guard that redirects before the loader ever runs, preventing the error path from triggering:
export const Route = createFileRoute("/protected")({
ssr: false,
beforeLoad: async () => {
const session = await getSessionGuardFn()
if (!session.authenticated) throw redirect({ to: "/login" })
},
loader: async () => {
await collection.preload()
return null
},
component: ProtectedPage,
})
This prevents the error path entirely, but the underlying issue remains — if any unexpected error occurs in the loader of an ssr: false route, the client will attempt to import server rendering modules.
What we've tried (that does NOT work)
- Adding
react-dom/server to Vite's optimizeDeps.include — breaks production build
- Adding
react-dom/server.browser.js to optimizeDeps.exclude — no effect
- Manual aliases for
react-dom/server — breaks other SSR paths
@tanstack/react-start-plugin interop mode — unrelated
None of these are correct fixes. The issue is that renderRouterToString should not be in the client import graph for ssr: false routes.
Screenshots or Videos
Dev console output:
Uncaught (in promise) SyntaxError: The requested module
'/node_modules/.pnpm/react-dom@19.2.6_react@19.2.6/node_modules/react-dom/server.browser.js?v=b87af0c2'
does not provide an export named 'default' (at renderRouterToString.js?v=b87af0c2:1:8)
Platform
@tanstack/react-start@1.169.2
@tanstack/react-router@1.169.2
- React 19.2.6
- Vite+ (Vite 8 + Rolldown) via
vp CLI
- Node 22
- OS: Linux (Ubuntu)
- Browser: Chrome 136
Additional context
- Production builds (
vite build + vite preview) do not exhibit this issue — only the dev server
- The error is deterministic: it fires every time an
ssr: false route's loader throws in dev
- We've confirmed via bundle inspection that the production client bundle contains zero
react-dom/server code, so tree-shaking/code-splitting handles it correctly at build time — the issue is specific to Vite's dev module resolution
This was also cross-posted to the Vite+ GitHub Discussions for visibility, but we believe the fix belongs in TanStack Router/Start's error boundary rendering path.
Which project does this relate to?
Start
Describe the bug
When a route configured with
ssr: falseencounters an error during itsbeforeLoadorloaderphase (e.g., an auth middleware throwingUNAUTHENTICATED), the error-handling path in the client environment attempts to importreact-dom/server.browser.jsviarenderRouterToString.js. This module does not provide a default export in the browser environment, resulting in:This only occurs in development — production builds are unaffected.
Root cause analysis
The import chain appears to be:
ssr: false+ aloaderthat calls acreateServerFnprotected by auth middlewarerenderRouterToStringrenderRouterToString.jsdoesimport ReactDOMServer from 'react-dom/server'react-dom/server.browser.jswhich is an ESM module that does not have a default export — only named exports (renderToString,renderToStaticMarkup, etc.)Since the route explicitly opts out of SSR (
ssr: false), the error boundary should never need server-side rendering utilities in the client graph.Complete minimal reproducer
Project: hendripermana/permoney (branch
feat/m1-5-stability-gate)Minimal route shape that triggers the issue:
Steps to Reproduce the Bug
ssr: falseloaderthat calls acreateServerFnwhich throws an error (e.g., auth check fails)vite dev(or equivalent)react-dom/server.browser.jsnot providing a default exportExpected behavior
For
ssr: falseroutes, the error boundary rendering path should not importreact-dom/serverin the client environment. The error should be handled entirely client-side (e.g., rendering the route'serrorComponentwithout server rendering utilities).Workaround
Add a
beforeLoadguard that redirects before the loader ever runs, preventing the error path from triggering:This prevents the error path entirely, but the underlying issue remains — if any unexpected error occurs in the loader of an
ssr: falseroute, the client will attempt to import server rendering modules.What we've tried (that does NOT work)
react-dom/serverto Vite'soptimizeDeps.include— breaks production buildreact-dom/server.browser.jstooptimizeDeps.exclude— no effectreact-dom/server— breaks other SSR paths@tanstack/react-start-plugininterop mode — unrelatedNone of these are correct fixes. The issue is that
renderRouterToStringshould not be in the client import graph forssr: falseroutes.Screenshots or Videos
Dev console output:
Platform
@tanstack/react-start@1.169.2@tanstack/react-router@1.169.2vpCLIAdditional context
vite build+vite preview) do not exhibit this issue — only the dev serverssr: falseroute's loader throws in devreact-dom/servercode, so tree-shaking/code-splitting handles it correctly at build time — the issue is specific to Vite's dev module resolutionThis was also cross-posted to the Vite+ GitHub Discussions for visibility, but we believe the fix belongs in TanStack Router/Start's error boundary rendering path.