Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions labeler-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
'package: router-generator':
- changed-files:
- any-glob-to-any-file: 'packages/router-generator/**/*'
'package: router-is-server':
- changed-files:
- any-glob-to-any-file: 'packages/router-is-server/**/*'
'package: router-plugin':
- changed-files:
- any-glob-to-any-file: 'packages/router-plugin/**/*'
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@tanstack/react-query": "5.66.0",
"use-sync-external-store": "1.2.2",
"@tanstack/history": "workspace:*",
"@tanstack/router-is-server": "workspace:*",
"@tanstack/router-core": "workspace:*",
"@tanstack/react-router": "workspace:*",
"@tanstack/router-cli": "workspace:*",
Expand Down
1 change: 1 addition & 0 deletions packages/react-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"@tanstack/history": "workspace:*",
"@tanstack/react-store": "^0.7.0",
"@tanstack/router-core": "workspace:*",
"@tanstack/router-is-server": "workspace:*",
"isbot": "^5.1.22",
"tiny-invariant": "^1.3.3",
"tiny-warning": "^1.0.3"
Expand Down
5 changes: 3 additions & 2 deletions packages/react-router/src/Match.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
pick,
rootRouteId,
} from '@tanstack/router-core'
import { isServer } from '@tanstack/router-is-server'
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
import { useRouterState } from './useRouterState'
import { useRouter } from './useRouter'
Expand Down Expand Up @@ -242,7 +243,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({

if (pendingMinMs && !router.getMatch(match.id)?.minPendingPromise) {
// Create a promise that will resolve after the minPendingMs
if (!router.isServer) {
if (!isServer) {
const minPendingPromise = createControlledPromise<void>()

Promise.resolve().then(() => {
Expand Down Expand Up @@ -289,7 +290,7 @@ export const MatchInner = React.memo(function MatchInnerImpl({
// of a suspense boundary. This is the only way to get
// renderToPipeableStream to not hang indefinitely.
// We'll serialize the error and rethrow it on the client.
if (router.isServer) {
if (isServer) {
const RouteErrorComponent =
(route.options.errorComponent ??
router.options.defaultErrorComponent) ||
Expand Down
3 changes: 2 additions & 1 deletion packages/react-router/src/Matches.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react'
import warning from 'tiny-warning'
import { isServer } from '@tanstack/router-is-server'
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
import { useRouterState } from './useRouterState'
import { useRouter } from './useRouter'
Expand Down Expand Up @@ -48,7 +49,7 @@ export function Matches() {

// Do not render a root Suspense during SSR or hydrating from SSR
const ResolvedSuspense =
router.isServer || (typeof document !== 'undefined' && router.ssr)
isServer || (typeof document !== 'undefined' && router.ssr)
? SafeFragment
: React.Suspense

Expand Down
3 changes: 2 additions & 1 deletion packages/react-router/src/Transitioner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
handleHashScroll,
trimPathRight,
} from '@tanstack/router-core'
import { isServer } from '@tanstack/router-is-server'
import { useLayoutEffect, usePrevious } from './utils'
import { useRouter } from './useRouter'
import { useRouterState } from './useRouterState'
Expand All @@ -30,7 +31,7 @@ export function Transitioner() {
const isPagePending = isLoading || hasPendingMatches
const previousIsPagePending = usePrevious(isPagePending)

if (!router.isServer) {
if (!isServer) {
router.startTransition = (fn: () => void) => {
setIsTransitioning(true)
React.startTransition(() => {
Expand Down
3 changes: 2 additions & 1 deletion packages/react-router/src/scroll-restoration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
restoreScroll,
storageKey,
} from '@tanstack/router-core'
import { isServer } from '@tanstack/router-is-server'
import { useRouter } from './useRouter'
import { ScriptOnce } from './ScriptOnce'

Expand All @@ -16,7 +17,7 @@ export function ScrollRestoration() {
? userKey
: null

if (!router.isScrollRestoring || !router.isServer) {
if (!router.isScrollRestoring || !isServer) {
return null
}

Expand Down
17 changes: 12 additions & 5 deletions packages/react-router/tests/ClientOnly.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ afterEach(() => {
cleanup()
})

function createTestRouter(opts: { isServer: boolean }) {
vi.mock('@tanstack/router-is-server', () => ({
isServer: true,
}))

function createTestRouter() {
const history = createMemoryHistory({ initialEntries: ['/'] })

const rootRoute = createRootRoute({})
Expand Down Expand Up @@ -44,7 +48,7 @@ function createTestRouter(opts: { isServer: boolean }) {
})

const routeTree = rootRoute.addChildren([indexRoute, otherRoute])
const router = createRouter({ routeTree, history, ...opts })
const router = createRouter({ routeTree, history })

return {
router,
Expand All @@ -54,7 +58,7 @@ function createTestRouter(opts: { isServer: boolean }) {

describe('ClientOnly', () => {
it('should render fallback during SSR', async () => {
const { router } = createTestRouter({ isServer: true })
const { router } = createTestRouter()
await router.load()

// Initial render (SSR)
Expand All @@ -66,7 +70,9 @@ describe('ClientOnly', () => {
})

it('should render client content after hydration', async () => {
const { router } = createTestRouter({ isServer: false })
vi.doUnmock('@tanstack/router-is-server')

const { router } = createTestRouter()
await router.load()

// Mock useSyncExternalStore to simulate hydration
Expand All @@ -79,7 +85,8 @@ describe('ClientOnly', () => {
})

it('should handle navigation with client-only content', async () => {
const { router } = createTestRouter({ isServer: false })
vi.doUnmock('@tanstack/router-is-server')
const { router } = createTestRouter()
await router.load()

// Simulate hydration
Expand Down
21 changes: 17 additions & 4 deletions packages/react-router/tests/Scripts.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, test } from 'vitest'
import { afterEach, describe, expect, test, vi } from 'vitest'
import { act, render, screen } from '@testing-library/react'
import ReactDOMServer from 'react-dom/server'

Expand All @@ -13,8 +13,16 @@ import {
} from '../src'
import { Scripts } from '../src/Scripts'

afterEach(() => {
vi.resetAllMocks()
})

describe('ssr scripts', () => {
test('it works', async () => {
vi.mock('@tanstack/router-is-server', () => ({
isServer: true,
}))

const rootRoute = createRootRoute({
head: () => {
return {
Expand Down Expand Up @@ -58,7 +66,6 @@ describe('ssr scripts', () => {
initialEntries: ['/'],
}),
routeTree: rootRoute.addChildren([indexRoute]),
isServer: true,
})

await router.load()
Expand All @@ -71,6 +78,10 @@ describe('ssr scripts', () => {
})

test('excludes `undefined` script values', async () => {
vi.mock('@tanstack/router-is-server', () => ({
isServer: true,
}))

const rootRoute = createRootRoute({
scripts: () => [
{ src: 'script.js' },
Expand Down Expand Up @@ -101,7 +112,6 @@ describe('ssr scripts', () => {
initialEntries: ['/'],
}),
routeTree: rootRoute.addChildren([indexRoute]),
isServer: true,
})

await router.load()
Expand All @@ -126,6 +136,10 @@ describe('ssr scripts', () => {

describe('ssr HeadContent', () => {
test('derives title, dedupes meta, and allows non-loader HeadContent', async () => {
vi.mock('@tanstack/router-is-server', () => ({
isServer: true,
}))

const rootRoute = createRootRoute({
loader: () =>
new Promise((r) => setTimeout(r, 1)).then(() => ({
Expand Down Expand Up @@ -196,7 +210,6 @@ describe('ssr HeadContent', () => {
initialEntries: ['/'],
}),
routeTree: rootRoute.addChildren([indexRoute]),
isServer: true,
})

await router.load()
Expand Down
12 changes: 7 additions & 5 deletions packages/react-router/tests/redirect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {

import { sleep } from './utils'
import type { RouterHistory } from '../src'

let history: RouterHistory

beforeEach(() => {
Expand All @@ -41,8 +40,15 @@ afterEach(() => {

const WAIT_TIME = 100

vi.mock('@tanstack/router-is-server', () => ({
isServer: false,
}))

describe('redirect', () => {
describe('SPA', () => {
beforeEach(() => {
vi.doUnmock('@tanstack/router-is-server')
})
configure({ reactStrictMode: true })
test('when `redirect` is thrown in `beforeLoad`', async () => {
const nestedLoaderMock = vi.fn()
Expand Down Expand Up @@ -289,8 +295,6 @@ describe('redirect', () => {

const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, aboutRoute]),
// Mock server mode
isServer: true,
history: createMemoryHistory({
initialEntries: ['/'],
}),
Expand Down Expand Up @@ -342,8 +346,6 @@ describe('redirect', () => {
initialEntries: ['/'],
}),
routeTree: rootRoute.addChildren([indexRoute, aboutRoute]),
// Mock server mode
isServer: true,
})

await router.load()
Expand Down
7 changes: 2 additions & 5 deletions packages/react-router/tests/useNavigate.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1777,7 +1777,7 @@ test.each([true, false])(
)

describe('when on /posts/$postId and navigating to ../ with default `from` /posts', () => {
async function runTest(navigateVia: 'Route' | 'RouteApi') {
test.each(['Route', 'RouteApi'])('%s', async (navigateVia) => {
const rootRoute = createRootRoute()

const IndexComponent = () => {
Expand Down Expand Up @@ -1920,10 +1920,7 @@ describe('when on /posts/$postId and navigating to ../ with default `from` /post

expect(await screen.findByTestId('index-heading')).toBeInTheDocument()
expect(window.location.pathname).toEqual('/')
}

test('Route', () => runTest('Route'))
test('RouteApi', () => runTest('RouteApi'))
})
})

describe.each([{ basepath: '' }, { basepath: '/basepath' }])(
Expand Down
5 changes: 5 additions & 0 deletions packages/react-router/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ const config = defineConfig({
typecheck: { enabled: true },
setupFiles: ['./tests/setupTests.tsx'],
},
resolve: {
// this is necessary so that @tanstack/router-is-server resolves to `isServer: false`
// for some server side tests we mock this package to get `isServer: true`
conditions: ['browser'],
},
})

export default mergeConfig(
Expand Down
1 change: 1 addition & 0 deletions packages/router-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"dependencies": {
"@tanstack/history": "workspace:*",
"@tanstack/store": "^0.7.0",
"@tanstack/router-is-server": "workspace:*",
"cookie-es": "^1.2.2",
"seroval": "^1.3.2",
"seroval-plugins": "^1.3.2",
Expand Down
Loading
Loading