Skip to content

[Start] ssr: false route error path imports react-dom/server.browser.js in client environment #7412

@hendripermana

Description

@hendripermana

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:

  1. Route has ssr: false + a loader that calls a createServerFn protected by auth middleware
  2. Unauthenticated user navigates to the route → middleware throws
  3. TanStack Router's error boundary rendering path pulls in renderRouterToString
  4. renderRouterToString.js does import ReactDOMServer from 'react-dom/server'
  5. 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.)
  6. 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

  1. Create a TanStack Start app with a route that has ssr: false
  2. Add a loader that calls a createServerFn which throws an error (e.g., auth check fails)
  3. Run vite dev (or equivalent)
  4. Navigate to the route while unauthenticated
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions