From 449d31a644ef72b232d6fe1a4a7b4cd30b88a465 Mon Sep 17 00:00:00 2001 From: Manuel Schiller Date: Mon, 13 Apr 2026 22:45:29 +0200 Subject: [PATCH] fix: add react-server server export for react-start --- .changeset/green-rabbits-share.md | 7 ++++ e2e/react-start/rsc/src/routeTree.gen.ts | 21 ++++++++++ e2e/react-start/rsc/src/routes/__root.tsx | 8 ++++ e2e/react-start/rsc/src/routes/index.tsx | 7 ++++ .../rsc/src/routes/rsc-request-headers.tsx | 38 +++++++++++++++++++ .../rsc/tests/rsc-request-headers.spec.ts | 28 ++++++++++++++ packages/react-start/package.json | 4 ++ packages/react-start/src/server.rsc.ts | 1 + packages/react-start/vite.config.ts | 1 + 9 files changed, 115 insertions(+) create mode 100644 .changeset/green-rabbits-share.md create mode 100644 e2e/react-start/rsc/src/routes/rsc-request-headers.tsx create mode 100644 e2e/react-start/rsc/tests/rsc-request-headers.spec.ts create mode 100644 packages/react-start/src/server.rsc.ts diff --git a/.changeset/green-rabbits-share.md b/.changeset/green-rabbits-share.md new file mode 100644 index 0000000000..1c53b6d986 --- /dev/null +++ b/.changeset/green-rabbits-share.md @@ -0,0 +1,7 @@ +--- +'@tanstack/react-start': patch +--- + +Fix `@tanstack/react-start/server` imports inside React Server Components by adding a `react-server` export condition that resolves to the request/response APIs without pulling in the SSR renderer entrypoints. + +This fixes RSC routes that call `createServerFn` loaders and read request headers in dev with `@vitejs/plugin-rsc` enabled. diff --git a/e2e/react-start/rsc/src/routeTree.gen.ts b/e2e/react-start/rsc/src/routeTree.gen.ts index 60280db3a1..d5074d36e0 100644 --- a/e2e/react-start/rsc/src/routeTree.gen.ts +++ b/e2e/react-start/rsc/src/routeTree.gen.ts @@ -19,6 +19,7 @@ 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 RscRequestHeadersRouteImport } from './routes/rsc-request-headers' import { Route as RscReactCacheRouteImport } from './routes/rsc-react-cache' import { Route as RscParallelRouteImport } from './routes/rsc-parallel' import { Route as RscNestedStructureRouteImport } from './routes/rsc-nested-structure' @@ -102,6 +103,11 @@ const RscSlotJsxArgsRoute = RscSlotJsxArgsRouteImport.update({ path: '/rsc-slot-jsx-args', getParentRoute: () => rootRouteImport, } as any) +const RscRequestHeadersRoute = RscRequestHeadersRouteImport.update({ + id: '/rsc-request-headers', + path: '/rsc-request-headers', + getParentRoute: () => rootRouteImport, +} as any) const RscReactCacheRoute = RscReactCacheRouteImport.update({ id: '/rsc-react-cache', path: '/rsc-react-cache', @@ -291,6 +297,7 @@ export interface FileRoutesByFullPath { '/rsc-nested-structure': typeof RscNestedStructureRoute '/rsc-parallel': typeof RscParallelRoute '/rsc-react-cache': typeof RscReactCacheRoute + '/rsc-request-headers': typeof RscRequestHeadersRoute '/rsc-slot-jsx-args': typeof RscSlotJsxArgsRoute '/rsc-slots': typeof RscSlotsRoute '/rsc-ssr-data-only': typeof RscSsrDataOnlyRoute @@ -335,6 +342,7 @@ export interface FileRoutesByTo { '/rsc-nested-structure': typeof RscNestedStructureRoute '/rsc-parallel': typeof RscParallelRoute '/rsc-react-cache': typeof RscReactCacheRoute + '/rsc-request-headers': typeof RscRequestHeadersRoute '/rsc-slot-jsx-args': typeof RscSlotJsxArgsRoute '/rsc-slots': typeof RscSlotsRoute '/rsc-ssr-data-only': typeof RscSsrDataOnlyRoute @@ -380,6 +388,7 @@ export interface FileRoutesById { '/rsc-nested-structure': typeof RscNestedStructureRoute '/rsc-parallel': typeof RscParallelRoute '/rsc-react-cache': typeof RscReactCacheRoute + '/rsc-request-headers': typeof RscRequestHeadersRoute '/rsc-slot-jsx-args': typeof RscSlotJsxArgsRoute '/rsc-slots': typeof RscSlotsRoute '/rsc-ssr-data-only': typeof RscSsrDataOnlyRoute @@ -426,6 +435,7 @@ export interface FileRouteTypes { | '/rsc-nested-structure' | '/rsc-parallel' | '/rsc-react-cache' + | '/rsc-request-headers' | '/rsc-slot-jsx-args' | '/rsc-slots' | '/rsc-ssr-data-only' @@ -470,6 +480,7 @@ export interface FileRouteTypes { | '/rsc-nested-structure' | '/rsc-parallel' | '/rsc-react-cache' + | '/rsc-request-headers' | '/rsc-slot-jsx-args' | '/rsc-slots' | '/rsc-ssr-data-only' @@ -514,6 +525,7 @@ export interface FileRouteTypes { | '/rsc-nested-structure' | '/rsc-parallel' | '/rsc-react-cache' + | '/rsc-request-headers' | '/rsc-slot-jsx-args' | '/rsc-slots' | '/rsc-ssr-data-only' @@ -559,6 +571,7 @@ export interface RootRouteChildren { RscNestedStructureRoute: typeof RscNestedStructureRoute RscParallelRoute: typeof RscParallelRoute RscReactCacheRoute: typeof RscReactCacheRoute + RscRequestHeadersRoute: typeof RscRequestHeadersRoute RscSlotJsxArgsRoute: typeof RscSlotJsxArgsRoute RscSlotsRoute: typeof RscSlotsRoute RscSsrDataOnlyRoute: typeof RscSsrDataOnlyRoute @@ -648,6 +661,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RscSlotJsxArgsRouteImport parentRoute: typeof rootRouteImport } + '/rsc-request-headers': { + id: '/rsc-request-headers' + path: '/rsc-request-headers' + fullPath: '/rsc-request-headers' + preLoaderRoute: typeof RscRequestHeadersRouteImport + parentRoute: typeof rootRouteImport + } '/rsc-react-cache': { id: '/rsc-react-cache' path: '/rsc-react-cache' @@ -903,6 +923,7 @@ const rootRouteChildren: RootRouteChildren = { RscNestedStructureRoute: RscNestedStructureRoute, RscParallelRoute: RscParallelRoute, RscReactCacheRoute: RscReactCacheRoute, + RscRequestHeadersRoute: RscRequestHeadersRoute, 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 8939476b1a..ac169e7e94 100644 --- a/e2e/react-start/rsc/src/routes/__root.tsx +++ b/e2e/react-start/rsc/src/routes/__root.tsx @@ -304,6 +304,14 @@ function RootComponent() { > Global CSS + + Request Headers + { + return getRequestHeaders().get('cookie') ?? '' +}) + +export const Route = createFileRoute('/rsc-request-headers')({ + loader: async () => { + const cookies = await getCookies() + + return { + cookies, + } + }, + component: RscRequestHeadersComponent, +}) + +function RscRequestHeadersComponent() { + const { cookies } = Route.useLoaderData() + + return ( +
+

+ RSC Request Headers +

+

+ A route loader calling a server function can read request headers in the + RSC environment. +

+
{cookies}
+
+ ) +} diff --git a/e2e/react-start/rsc/tests/rsc-request-headers.spec.ts b/e2e/react-start/rsc/tests/rsc-request-headers.spec.ts new file mode 100644 index 0000000000..95ee787d3a --- /dev/null +++ b/e2e/react-start/rsc/tests/rsc-request-headers.spec.ts @@ -0,0 +1,28 @@ +import { expect } from '@playwright/test' +import { test } from '@tanstack/router-e2e-utils' + +test.describe('RSC Request Headers Tests', () => { + test('loader server functions can read cookies with vite-rsc enabled', async ({ + page, + context, + }) => { + await context.setExtraHTTPHeaders({ + Cookie: 'session=abc123; theme=dark', + }) + + const response = await page.goto('/rsc-request-headers') + await page.waitForURL('/rsc-request-headers') + + expect(response?.status()).toBe(200) + + await expect(page.getByTestId('rsc-request-headers-title')).toHaveText( + 'RSC Request Headers', + ) + await expect(page.getByTestId('rsc-request-headers-cookies')).toContainText( + 'session=abc123', + ) + await expect(page.getByTestId('rsc-request-headers-cookies')).toContainText( + 'theme=dark', + ) + }) +}) diff --git a/packages/react-start/package.json b/packages/react-start/package.json index 89b1e86e17..1ae266a728 100644 --- a/packages/react-start/package.json +++ b/packages/react-start/package.json @@ -55,6 +55,10 @@ } }, "./server": { + "react-server": { + "types": "./dist/esm/server.rsc.d.ts", + "default": "./dist/esm/server.rsc.js" + }, "import": { "types": "./dist/esm/server.d.ts", "default": "./dist/esm/server.js" diff --git a/packages/react-start/src/server.rsc.ts b/packages/react-start/src/server.rsc.ts new file mode 100644 index 0000000000..32383a58dc --- /dev/null +++ b/packages/react-start/src/server.rsc.ts @@ -0,0 +1 @@ +export * from '@tanstack/start-server-core' diff --git a/packages/react-start/vite.config.ts b/packages/react-start/vite.config.ts index 1e5349d41a..20c0e5d69a 100644 --- a/packages/react-start/vite.config.ts +++ b/packages/react-start/vite.config.ts @@ -30,6 +30,7 @@ export default mergeConfig( './src/client.tsx', './src/client-rpc.ts', './src/server.tsx', + './src/server.rsc.ts', './src/server-rpc.ts', './src/ssr-rpc.ts', './src/rsc.tsx',