Skip to content

Commit 7324b98

Browse files
fix: add react-server server export for react-start (#7180)
1 parent c09aab6 commit 7324b98

9 files changed

Lines changed: 115 additions & 0 deletions

File tree

.changeset/green-rabbits-share.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@tanstack/react-start': patch
3+
---
4+
5+
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.
6+
7+
This fixes RSC routes that call `createServerFn` loaders and read request headers in dev with `@vitejs/plugin-rsc` enabled.

e2e/react-start/rsc/src/routeTree.gen.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Route as RscSsrFalseRouteImport } from './routes/rsc-ssr-false'
1919
import { Route as RscSsrDataOnlyRouteImport } from './routes/rsc-ssr-data-only'
2020
import { Route as RscSlotsRouteImport } from './routes/rsc-slots'
2121
import { Route as RscSlotJsxArgsRouteImport } from './routes/rsc-slot-jsx-args'
22+
import { Route as RscRequestHeadersRouteImport } from './routes/rsc-request-headers'
2223
import { Route as RscReactCacheRouteImport } from './routes/rsc-react-cache'
2324
import { Route as RscParallelRouteImport } from './routes/rsc-parallel'
2425
import { Route as RscNestedStructureRouteImport } from './routes/rsc-nested-structure'
@@ -102,6 +103,11 @@ const RscSlotJsxArgsRoute = RscSlotJsxArgsRouteImport.update({
102103
path: '/rsc-slot-jsx-args',
103104
getParentRoute: () => rootRouteImport,
104105
} as any)
106+
const RscRequestHeadersRoute = RscRequestHeadersRouteImport.update({
107+
id: '/rsc-request-headers',
108+
path: '/rsc-request-headers',
109+
getParentRoute: () => rootRouteImport,
110+
} as any)
105111
const RscReactCacheRoute = RscReactCacheRouteImport.update({
106112
id: '/rsc-react-cache',
107113
path: '/rsc-react-cache',
@@ -291,6 +297,7 @@ export interface FileRoutesByFullPath {
291297
'/rsc-nested-structure': typeof RscNestedStructureRoute
292298
'/rsc-parallel': typeof RscParallelRoute
293299
'/rsc-react-cache': typeof RscReactCacheRoute
300+
'/rsc-request-headers': typeof RscRequestHeadersRoute
294301
'/rsc-slot-jsx-args': typeof RscSlotJsxArgsRoute
295302
'/rsc-slots': typeof RscSlotsRoute
296303
'/rsc-ssr-data-only': typeof RscSsrDataOnlyRoute
@@ -335,6 +342,7 @@ export interface FileRoutesByTo {
335342
'/rsc-nested-structure': typeof RscNestedStructureRoute
336343
'/rsc-parallel': typeof RscParallelRoute
337344
'/rsc-react-cache': typeof RscReactCacheRoute
345+
'/rsc-request-headers': typeof RscRequestHeadersRoute
338346
'/rsc-slot-jsx-args': typeof RscSlotJsxArgsRoute
339347
'/rsc-slots': typeof RscSlotsRoute
340348
'/rsc-ssr-data-only': typeof RscSsrDataOnlyRoute
@@ -380,6 +388,7 @@ export interface FileRoutesById {
380388
'/rsc-nested-structure': typeof RscNestedStructureRoute
381389
'/rsc-parallel': typeof RscParallelRoute
382390
'/rsc-react-cache': typeof RscReactCacheRoute
391+
'/rsc-request-headers': typeof RscRequestHeadersRoute
383392
'/rsc-slot-jsx-args': typeof RscSlotJsxArgsRoute
384393
'/rsc-slots': typeof RscSlotsRoute
385394
'/rsc-ssr-data-only': typeof RscSsrDataOnlyRoute
@@ -426,6 +435,7 @@ export interface FileRouteTypes {
426435
| '/rsc-nested-structure'
427436
| '/rsc-parallel'
428437
| '/rsc-react-cache'
438+
| '/rsc-request-headers'
429439
| '/rsc-slot-jsx-args'
430440
| '/rsc-slots'
431441
| '/rsc-ssr-data-only'
@@ -470,6 +480,7 @@ export interface FileRouteTypes {
470480
| '/rsc-nested-structure'
471481
| '/rsc-parallel'
472482
| '/rsc-react-cache'
483+
| '/rsc-request-headers'
473484
| '/rsc-slot-jsx-args'
474485
| '/rsc-slots'
475486
| '/rsc-ssr-data-only'
@@ -514,6 +525,7 @@ export interface FileRouteTypes {
514525
| '/rsc-nested-structure'
515526
| '/rsc-parallel'
516527
| '/rsc-react-cache'
528+
| '/rsc-request-headers'
517529
| '/rsc-slot-jsx-args'
518530
| '/rsc-slots'
519531
| '/rsc-ssr-data-only'
@@ -559,6 +571,7 @@ export interface RootRouteChildren {
559571
RscNestedStructureRoute: typeof RscNestedStructureRoute
560572
RscParallelRoute: typeof RscParallelRoute
561573
RscReactCacheRoute: typeof RscReactCacheRoute
574+
RscRequestHeadersRoute: typeof RscRequestHeadersRoute
562575
RscSlotJsxArgsRoute: typeof RscSlotJsxArgsRoute
563576
RscSlotsRoute: typeof RscSlotsRoute
564577
RscSsrDataOnlyRoute: typeof RscSsrDataOnlyRoute
@@ -648,6 +661,13 @@ declare module '@tanstack/react-router' {
648661
preLoaderRoute: typeof RscSlotJsxArgsRouteImport
649662
parentRoute: typeof rootRouteImport
650663
}
664+
'/rsc-request-headers': {
665+
id: '/rsc-request-headers'
666+
path: '/rsc-request-headers'
667+
fullPath: '/rsc-request-headers'
668+
preLoaderRoute: typeof RscRequestHeadersRouteImport
669+
parentRoute: typeof rootRouteImport
670+
}
651671
'/rsc-react-cache': {
652672
id: '/rsc-react-cache'
653673
path: '/rsc-react-cache'
@@ -903,6 +923,7 @@ const rootRouteChildren: RootRouteChildren = {
903923
RscNestedStructureRoute: RscNestedStructureRoute,
904924
RscParallelRoute: RscParallelRoute,
905925
RscReactCacheRoute: RscReactCacheRoute,
926+
RscRequestHeadersRoute: RscRequestHeadersRoute,
906927
RscSlotJsxArgsRoute: RscSlotJsxArgsRoute,
907928
RscSlotsRoute: RscSlotsRoute,
908929
RscSsrDataOnlyRoute: RscSsrDataOnlyRoute,

e2e/react-start/rsc/src/routes/__root.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,14 @@ function RootComponent() {
304304
>
305305
Global CSS
306306
</Link>
307+
<Link
308+
to="/rsc-request-headers"
309+
className="nav-link"
310+
activeProps={{ className: 'nav-link active' }}
311+
data-testid="nav-request-headers"
312+
>
313+
Request Headers
314+
</Link>
307315
<Link
308316
to="/rsc-react-cache"
309317
className="nav-link"

e2e/react-start/rsc/src/routes/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,13 @@ const examples = linkOptions([
202202
'Global CSS in server components - demonstrates plain CSS imports (import "styles.css") within RSC',
203203
icon: '🖌️',
204204
},
205+
{
206+
to: '/rsc-request-headers',
207+
title: 'RSC Request Headers',
208+
description:
209+
'Loader calls a server function that reads the incoming Cookie header in the RSC environment',
210+
icon: '🍪',
211+
},
205212
{
206213
to: '/rsc-react-cache',
207214
title: 'RSC React.cache',
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { createFileRoute } from '@tanstack/react-router'
2+
import { createServerFn } from '@tanstack/react-start'
3+
import { getRequestHeaders } from '@tanstack/react-start/server'
4+
import { pageStyles } from '~/utils/styles'
5+
6+
const getCookies = createServerFn({
7+
method: 'GET',
8+
}).handler(async () => {
9+
return getRequestHeaders().get('cookie') ?? ''
10+
})
11+
12+
export const Route = createFileRoute('/rsc-request-headers')({
13+
loader: async () => {
14+
const cookies = await getCookies()
15+
16+
return {
17+
cookies,
18+
}
19+
},
20+
component: RscRequestHeadersComponent,
21+
})
22+
23+
function RscRequestHeadersComponent() {
24+
const { cookies } = Route.useLoaderData()
25+
26+
return (
27+
<div style={pageStyles.container}>
28+
<h1 data-testid="rsc-request-headers-title" style={pageStyles.title}>
29+
RSC Request Headers
30+
</h1>
31+
<p style={pageStyles.description}>
32+
A route loader calling a server function can read request headers in the
33+
RSC environment.
34+
</p>
35+
<pre data-testid="rsc-request-headers-cookies">{cookies}</pre>
36+
</div>
37+
)
38+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { expect } from '@playwright/test'
2+
import { test } from '@tanstack/router-e2e-utils'
3+
4+
test.describe('RSC Request Headers Tests', () => {
5+
test('loader server functions can read cookies with vite-rsc enabled', async ({
6+
page,
7+
context,
8+
}) => {
9+
await context.setExtraHTTPHeaders({
10+
Cookie: 'session=abc123; theme=dark',
11+
})
12+
13+
const response = await page.goto('/rsc-request-headers')
14+
await page.waitForURL('/rsc-request-headers')
15+
16+
expect(response?.status()).toBe(200)
17+
18+
await expect(page.getByTestId('rsc-request-headers-title')).toHaveText(
19+
'RSC Request Headers',
20+
)
21+
await expect(page.getByTestId('rsc-request-headers-cookies')).toContainText(
22+
'session=abc123',
23+
)
24+
await expect(page.getByTestId('rsc-request-headers-cookies')).toContainText(
25+
'theme=dark',
26+
)
27+
})
28+
})

packages/react-start/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@
5555
}
5656
},
5757
"./server": {
58+
"react-server": {
59+
"types": "./dist/esm/server.rsc.d.ts",
60+
"default": "./dist/esm/server.rsc.js"
61+
},
5862
"import": {
5963
"types": "./dist/esm/server.d.ts",
6064
"default": "./dist/esm/server.js"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from '@tanstack/start-server-core'

packages/react-start/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export default mergeConfig(
3030
'./src/client.tsx',
3131
'./src/client-rpc.ts',
3232
'./src/server.tsx',
33+
'./src/server.rsc.ts',
3334
'./src/server-rpc.ts',
3435
'./src/ssr-rpc.ts',
3536
'./src/rsc.tsx',

0 commit comments

Comments
 (0)