Skip to content

Commit 4b4562a

Browse files
fix: don't run router.load() when hydrating only SSRed matches (#4630)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 8df504c commit 4b4562a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1483
-190
lines changed

docs/start/framework/react/spa-mode.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,18 +142,18 @@ Customizing the HTML output of the SPA shell can be useful if you want to:
142142
- Provide a custom pending fallback component
143143
- Change literally anything about the shell's HTML, CSS, and JS
144144

145-
To make this process simple, an `isShell` boolean can be found on the `router` instance:
145+
To make this process simple, an `isShell()` function can be found on the `router` instance:
146146

147147
```tsx
148148
// src/routes/root.tsx
149149
export default function Root() {
150-
const isShell = useRouter().isShell
150+
const isShell = useRouter().isShell()
151151

152152
if (isShell) console.log('Rendering the shell!')
153153
}
154154
```
155155

156-
You can use this boolean to conditionally render different UI based on whether the current route is a shell or not, but keep in mind that after hydrating the shell, the router will immediately navigate to the first route and the `isShell` boolean will be `false`. **This could produce flashes of unstyled content if not handled properly.**
156+
You can use this boolean to conditionally render different UI based on whether the current route is a shell or not, but keep in mind that after hydrating the shell, the router will immediately navigate to the first route and `isShell()` will return `false`. **This could produce flashes of unstyled content if not handled properly.**
157157

158158
## Dynamic Data in your Shell
159159

e2e/react-start/selective-ssr/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
},
1313
"dependencies": {
1414
"@tanstack/react-router": "workspace:^",
15+
"@tanstack/react-router-devtools": "workspace:^",
1516
"@tanstack/react-start": "workspace:^",
1617
"react": "^19.0.0",
1718
"react-dom": "^19.0.0",

e2e/react-start/selective-ssr/src/routes/__root.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
/// <reference types="vite/client" />
22
import * as React from 'react'
33
import {
4+
ClientOnly,
45
HeadContent,
56
Link,
67
Outlet,
78
Scripts,
89
createRootRoute,
10+
useRouterState,
911
} from '@tanstack/react-router'
1012
import { z } from 'zod'
1113
import { ssrSchema } from '~/search'
1214
import appCss from '~/styles/app.css?url'
15+
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
1316

1417
export const Route = createRootRoute({
1518
head: () => ({
@@ -113,9 +116,14 @@ export const Route = createRootRoute({
113116
</div>
114117
)
115118
},
119+
pendingComponent: () => <div>__root Loading...</div>,
116120
})
117121

118122
function RootDocument({ children }: { children: React.ReactNode }) {
123+
const { isLoading, status } = useRouterState({
124+
select: (state) => ({ isLoading: state.isLoading, status: state.status }),
125+
structuralSharing: true,
126+
})
119127
return (
120128
<html>
121129
<head>
@@ -134,8 +142,19 @@ function RootDocument({ children }: { children: React.ReactNode }) {
134142
</Link>
135143
</div>
136144
<hr />
145+
<ClientOnly>
146+
<div>
147+
router isLoading:{' '}
148+
<b data-testid="router-isLoading">{isLoading ? 'true' : 'false'}</b>
149+
</div>
150+
<div>
151+
router status: <b data-testid="router-status">{status}</b>
152+
</div>
153+
</ClientOnly>
154+
<hr />
137155
{children}
138156
<Scripts />
157+
<TanStackRouterDevtools position="bottom-right" />
139158
</body>
140159
</html>
141160
)

e2e/react-start/selective-ssr/src/routes/index.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,26 @@ const testCases = [
9191
},
9292
}),
9393
},
94+
{
95+
link: linkOptions({
96+
...baseTestCase,
97+
search: {
98+
root: {
99+
ssr: true,
100+
expected: { data: 'server', render: 'server-and-client' },
101+
},
102+
posts: {
103+
ssr: 'data-only',
104+
expected: { data: 'server', render: 'client-only' },
105+
},
106+
postId: {
107+
ssr: undefined,
108+
109+
expected: { data: 'server', render: 'client-only' },
110+
},
111+
},
112+
}),
113+
},
94114
{
95115
link: linkOptions({
96116
...baseTestCase,

e2e/react-start/selective-ssr/src/routes/posts.$postId.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,5 @@ export const Route = createFileRoute('/posts/$postId')({
8787
</div>
8888
)
8989
},
90+
pendingComponent: () => <div>$postId Loading...</div>,
9091
})

e2e/react-start/selective-ssr/src/routes/posts.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,5 @@ export const Route = createFileRoute('/posts')({
8989
</div>
9090
)
9191
},
92+
pendingComponent: () => <div>posts Loading...</div>,
9293
})

e2e/react-start/selective-ssr/tests/app.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect } from '@playwright/test'
22
import { test } from './fixture'
33

4-
const testCount = 6
4+
const testCount = 7
55

66
test.describe('selective ssr', () => {
77
test('testcount matches', async ({ page }) => {
@@ -34,6 +34,8 @@ test.describe('selective ssr', () => {
3434
)
3535
}),
3636
)
37+
await expect(page.getByTestId('router-isLoading')).toContainText('false')
38+
await expect(page.getByTestId('router-status')).toContainText('idle')
3739
})
3840
}
3941
})

e2e/react-start/spa-mode/.gitignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
node_modules
2+
package-lock.json
3+
yarn.lock
4+
5+
.DS_Store
6+
.cache
7+
.env
8+
.vercel
9+
.output
10+
/build/
11+
/api/
12+
/server/build
13+
/public/build# Sentry Config File
14+
.env.sentry-build-plugin
15+
/test-results/
16+
/playwright-report/
17+
/blob-report/
18+
/playwright/.cache/
19+
20+
count.txt
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
**/build
2+
**/public
3+
pnpm-lock.yaml
4+
routeTree.gen.ts

e2e/react-start/spa-mode/package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "tanstack-react-start-e2e-spa-mode",
3+
"private": true,
4+
"sideEffects": false,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite dev --port 3000",
8+
"dev:e2e": "vite dev",
9+
"build": "vite build && tsc --noEmit",
10+
"start": "node .output/server/index.mjs",
11+
"test:e2e": "playwright test --project=chromium"
12+
},
13+
"dependencies": {
14+
"@tanstack/react-router": "workspace:^",
15+
"@tanstack/react-router-devtools": "workspace:^",
16+
"@tanstack/react-start": "workspace:^",
17+
"react": "^19.0.0",
18+
"react-dom": "^19.0.0"
19+
},
20+
"devDependencies": {
21+
"@tanstack/router-e2e-utils": "workspace:^",
22+
"@types/node": "^22.10.2",
23+
"@types/react": "^19.0.8",
24+
"@types/react-dom": "^19.0.3",
25+
"postcss": "^8.5.1",
26+
"tailwindcss": "^3.4.17",
27+
"typescript": "^5.7.2",
28+
"vite": "^6.3.5",
29+
"vite-tsconfig-paths": "^5.1.4"
30+
}
31+
}

0 commit comments

Comments
 (0)