Skip to content

Commit 5140f74

Browse files
feat: add is-server
1 parent 4a47c9f commit 5140f74

37 files changed

Lines changed: 310 additions & 68 deletions

e2e/react-start/flamegraph-bench/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"devDependencies": {
2020
"@platformatic/flame": "latest",
2121
"@vitejs/plugin-react": "^4.3.4",
22-
"nitro": "npm:nitro-nightly@latest",
22+
"nitro": "file:/Users/caligano/source/nitro",
2323
"autocannon": "^8.0.0",
2424
"concurrently": "9.2.1",
2525
"typescript": "^5.7.2",

packages/react-router/src/Asset.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from 'react'
2+
import { isServer } from '@tanstack/router-core'
23
import { useRouter } from './useRouter'
34
import type { RouterManagedTag } from '@tanstack/router-core'
45

@@ -143,7 +144,7 @@ function Script({
143144
return undefined
144145
}, [attrs, children])
145146

146-
if (!router.isServer) {
147+
if (!(isServer ?? router.isServer)) {
147148
const { src, ...rest } = attrs || {}
148149
// render an empty script on the client just to avoid hydration errors
149150
return (

packages/react-router/src/Match.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getLocationChangeInfo,
77
isNotFound,
88
isRedirect,
9+
isServer,
910
rootRouteId,
1011
} from '@tanstack/router-core'
1112
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
@@ -246,7 +247,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({
246247
const routerMatch = router.getMatch(match.id)
247248
if (routerMatch && !routerMatch._nonReactive.minPendingPromise) {
248249
// Create a promise that will resolve after the minPendingMs
249-
if (!router.isServer) {
250+
if (!(isServer ?? router.isServer)) {
250251
const minPendingPromise = createControlledPromise<void>()
251252

252253
routerMatch._nonReactive.minPendingPromise = minPendingPromise
@@ -285,7 +286,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({
285286
// of a suspense boundary. This is the only way to get
286287
// renderToPipeableStream to not hang indefinitely.
287288
// We'll serialize the error and rethrow it on the client.
288-
if (router.isServer) {
289+
if (isServer ?? router.isServer) {
289290
const RouteErrorComponent =
290291
(route.options.errorComponent ??
291292
router.options.defaultErrorComponent) ||

packages/react-router/src/Matches.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react'
22
import warning from 'tiny-warning'
3-
import { rootRouteId } from '@tanstack/router-core'
3+
import { isServer, rootRouteId } from '@tanstack/router-core'
44
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
55
import { useRouterState } from './useRouterState'
66
import { useRouter } from './useRouter'
@@ -56,13 +56,14 @@ export function Matches() {
5656

5757
// Do not render a root Suspense during SSR or hydrating from SSR
5858
const ResolvedSuspense =
59-
router.isServer || (typeof document !== 'undefined' && router.ssr)
59+
(isServer ?? router.isServer) ||
60+
(typeof document !== 'undefined' && router.ssr)
6061
? SafeFragment
6162
: React.Suspense
6263

6364
const inner = (
6465
<ResolvedSuspense fallback={pendingElement}>
65-
{!router.isServer && <Transitioner />}
66+
{!(isServer ?? router.isServer) && <Transitioner />}
6667
<MatchesInner />
6768
</ResolvedSuspense>
6869
)

packages/react-router/src/ScriptOnce.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { isServer } from '@tanstack/router-core'
12
import { useRouter } from './useRouter'
23

34
/**
45
* Server-only helper to emit a script tag exactly once during SSR.
56
*/
67
export function ScriptOnce({ children }: { children: string }) {
78
const router = useRouter()
8-
if (!router.isServer) {
9+
if (!(isServer ?? router.isServer)) {
910
return null
1011
}
1112

packages/react-router/src/scroll-restoration.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
defaultGetScrollRestorationKey,
33
escapeHtml,
4+
isServer,
45
restoreScroll,
56
storageKey,
67
} from '@tanstack/router-core'
@@ -9,7 +10,7 @@ import { ScriptOnce } from './ScriptOnce'
910

1011
export function ScrollRestoration() {
1112
const router = useRouter()
12-
if (!router.isScrollRestoring || !router.isServer) {
13+
if (!router.isScrollRestoring || !(isServer ?? router.isServer)) {
1314
return null
1415
}
1516
if (typeof router.options.scrollRestoration === 'function') {

packages/react-router/vite.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import packageJson from './package.json'
55

66
const config = defineConfig({
77
plugins: [react()],
8+
resolve: {
9+
// Add 'development' condition for tests to resolve @tanstack/router-is-server
10+
// to the development export (isServer = undefined) instead of node (isServer = true)
11+
conditions: process.env.VITEST ? ['development'] : [],
12+
},
813
test: {
914
name: packageJson.name,
1015
dir: './tests',

packages/router-core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
},
8181
"dependencies": {
8282
"@tanstack/history": "workspace:*",
83+
"@tanstack/router-is-server": "workspace:*",
8384
"@tanstack/store": "^0.8.0",
8485
"cookie-es": "^2.0.0",
8586
"seroval": "^1.4.2",

packages/router-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export {
105105
export { encode, decode } from './qss'
106106
export { rootRouteId } from './root'
107107
export type { RootRouteId } from './root'
108+
export { isServer } from './isServer'
108109

109110
export { BaseRoute, BaseRouteApi, BaseRootRoute } from './route'
110111
export type {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Static server/client detection for tree-shaking support.
3+
*
4+
* This file re-exports `isServer` from `@tanstack/router-is-server` which uses
5+
* conditional exports to provide different values based on the environment:
6+
*
7+
* - `browser` condition → `false` (client)
8+
* - `node`/`worker`/`deno`/`bun` → `true` (server)
9+
* - `development` condition → `undefined` (for tests, falls back to router.isServer)
10+
*
11+
* The bundler resolves the correct file at build time based on export conditions,
12+
* and since the value is a literal constant, dead code can be eliminated.
13+
*
14+
* @example
15+
* ```typescript
16+
* import { isServer } from '@tanstack/router-core'
17+
*
18+
* // The ?? operator provides fallback for development/test mode
19+
* if (isServer ?? router.isServer) {
20+
* // Server-only code - eliminated in client bundles
21+
* }
22+
* ```
23+
*/
24+
export { isServer } from '@tanstack/router-is-server'

0 commit comments

Comments
 (0)