diff --git a/.changeset/neat-melons-worry.md b/.changeset/neat-melons-worry.md new file mode 100644 index 0000000000..d9148ae10d --- /dev/null +++ b/.changeset/neat-melons-worry.md @@ -0,0 +1,7 @@ +--- +'@tanstack/react-router': patch +--- + +Fix React Server Component imports from `@tanstack/react-router` by adding a `react-server` root export that preserves the normal API surface while resolving `notFound` and `redirect` from a server-safe entry. + +This fixes RSC routes that throw `notFound()` or `redirect()` from server functions so they behave correctly during SSR and client navigation. diff --git a/e2e/react-start/rsc/src/routeTree.gen.ts b/e2e/react-start/rsc/src/routeTree.gen.ts index d5074d36e0..c06c950ea2 100644 --- a/e2e/react-start/rsc/src/routeTree.gen.ts +++ b/e2e/react-start/rsc/src/routeTree.gen.ts @@ -19,6 +19,8 @@ import { Route as RscSsrFalseRouteImport } from './routes/rsc-ssr-false' import { Route as RscSsrDataOnlyRouteImport } from './routes/rsc-ssr-data-only' import { Route as RscSlotsRouteImport } from './routes/rsc-slots' import { Route as RscSlotJsxArgsRouteImport } from './routes/rsc-slot-jsx-args' +import { Route as RscServerRedirectRouteImport } from './routes/rsc-server-redirect' +import { Route as RscServerNotFoundRouteImport } from './routes/rsc-server-not-found' import { Route as RscRequestHeadersRouteImport } from './routes/rsc-request-headers' import { Route as RscReactCacheRouteImport } from './routes/rsc-react-cache' import { Route as RscParallelRouteImport } from './routes/rsc-parallel' @@ -103,6 +105,16 @@ const RscSlotJsxArgsRoute = RscSlotJsxArgsRouteImport.update({ path: '/rsc-slot-jsx-args', getParentRoute: () => rootRouteImport, } as any) +const RscServerRedirectRoute = RscServerRedirectRouteImport.update({ + id: '/rsc-server-redirect', + path: '/rsc-server-redirect', + getParentRoute: () => rootRouteImport, +} as any) +const RscServerNotFoundRoute = RscServerNotFoundRouteImport.update({ + id: '/rsc-server-not-found', + path: '/rsc-server-not-found', + getParentRoute: () => rootRouteImport, +} as any) const RscRequestHeadersRoute = RscRequestHeadersRouteImport.update({ id: '/rsc-request-headers', path: '/rsc-request-headers', @@ -298,6 +310,8 @@ export interface FileRoutesByFullPath { '/rsc-parallel': typeof RscParallelRoute '/rsc-react-cache': typeof RscReactCacheRoute '/rsc-request-headers': typeof RscRequestHeadersRoute + '/rsc-server-not-found': typeof RscServerNotFoundRoute + '/rsc-server-redirect': typeof RscServerRedirectRoute '/rsc-slot-jsx-args': typeof RscSlotJsxArgsRoute '/rsc-slots': typeof RscSlotsRoute '/rsc-ssr-data-only': typeof RscSsrDataOnlyRoute @@ -343,6 +357,8 @@ export interface FileRoutesByTo { '/rsc-parallel': typeof RscParallelRoute '/rsc-react-cache': typeof RscReactCacheRoute '/rsc-request-headers': typeof RscRequestHeadersRoute + '/rsc-server-not-found': typeof RscServerNotFoundRoute + '/rsc-server-redirect': typeof RscServerRedirectRoute '/rsc-slot-jsx-args': typeof RscSlotJsxArgsRoute '/rsc-slots': typeof RscSlotsRoute '/rsc-ssr-data-only': typeof RscSsrDataOnlyRoute @@ -389,6 +405,8 @@ export interface FileRoutesById { '/rsc-parallel': typeof RscParallelRoute '/rsc-react-cache': typeof RscReactCacheRoute '/rsc-request-headers': typeof RscRequestHeadersRoute + '/rsc-server-not-found': typeof RscServerNotFoundRoute + '/rsc-server-redirect': typeof RscServerRedirectRoute '/rsc-slot-jsx-args': typeof RscSlotJsxArgsRoute '/rsc-slots': typeof RscSlotsRoute '/rsc-ssr-data-only': typeof RscSsrDataOnlyRoute @@ -436,6 +454,8 @@ export interface FileRouteTypes { | '/rsc-parallel' | '/rsc-react-cache' | '/rsc-request-headers' + | '/rsc-server-not-found' + | '/rsc-server-redirect' | '/rsc-slot-jsx-args' | '/rsc-slots' | '/rsc-ssr-data-only' @@ -481,6 +501,8 @@ export interface FileRouteTypes { | '/rsc-parallel' | '/rsc-react-cache' | '/rsc-request-headers' + | '/rsc-server-not-found' + | '/rsc-server-redirect' | '/rsc-slot-jsx-args' | '/rsc-slots' | '/rsc-ssr-data-only' @@ -526,6 +548,8 @@ export interface FileRouteTypes { | '/rsc-parallel' | '/rsc-react-cache' | '/rsc-request-headers' + | '/rsc-server-not-found' + | '/rsc-server-redirect' | '/rsc-slot-jsx-args' | '/rsc-slots' | '/rsc-ssr-data-only' @@ -572,6 +596,8 @@ export interface RootRouteChildren { RscParallelRoute: typeof RscParallelRoute RscReactCacheRoute: typeof RscReactCacheRoute RscRequestHeadersRoute: typeof RscRequestHeadersRoute + RscServerNotFoundRoute: typeof RscServerNotFoundRoute + RscServerRedirectRoute: typeof RscServerRedirectRoute RscSlotJsxArgsRoute: typeof RscSlotJsxArgsRoute RscSlotsRoute: typeof RscSlotsRoute RscSsrDataOnlyRoute: typeof RscSsrDataOnlyRoute @@ -661,6 +687,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RscSlotJsxArgsRouteImport parentRoute: typeof rootRouteImport } + '/rsc-server-redirect': { + id: '/rsc-server-redirect' + path: '/rsc-server-redirect' + fullPath: '/rsc-server-redirect' + preLoaderRoute: typeof RscServerRedirectRouteImport + parentRoute: typeof rootRouteImport + } + '/rsc-server-not-found': { + id: '/rsc-server-not-found' + path: '/rsc-server-not-found' + fullPath: '/rsc-server-not-found' + preLoaderRoute: typeof RscServerNotFoundRouteImport + parentRoute: typeof rootRouteImport + } '/rsc-request-headers': { id: '/rsc-request-headers' path: '/rsc-request-headers' @@ -924,6 +964,8 @@ const rootRouteChildren: RootRouteChildren = { RscParallelRoute: RscParallelRoute, RscReactCacheRoute: RscReactCacheRoute, RscRequestHeadersRoute: RscRequestHeadersRoute, + RscServerNotFoundRoute: RscServerNotFoundRoute, + RscServerRedirectRoute: RscServerRedirectRoute, RscSlotJsxArgsRoute: RscSlotJsxArgsRoute, RscSlotsRoute: RscSlotsRoute, RscSsrDataOnlyRoute: RscSsrDataOnlyRoute, diff --git a/e2e/react-start/rsc/src/routes/__root.tsx b/e2e/react-start/rsc/src/routes/__root.tsx index ac169e7e94..9ebb559527 100644 --- a/e2e/react-start/rsc/src/routes/__root.tsx +++ b/e2e/react-start/rsc/src/routes/__root.tsx @@ -312,6 +312,22 @@ function RootComponent() { > Request Headers + + Server Not Found + + + Server Redirect + { + throw notFound() +}) + +export const Route = createFileRoute('/rsc-server-not-found')({ + loader: async () => { + await getMissingResource() + }, + component: RscServerNotFoundComponent, + notFoundComponent: RscServerNotFoundBoundary, +}) + +function RscServerNotFoundComponent() { + return ( +
+ The server function threw `notFound()` and the route-level not found + boundary rendered instead of crashing. +
+ + Back home + +