@tanstack/router-core 1.167.3 → 1.167.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/intent.js +25 -0
- package/package.json +10 -3
- package/skills/router-core/SKILL.md +139 -0
- package/skills/router-core/auth-and-guards/SKILL.md +458 -0
- package/skills/router-core/code-splitting/SKILL.md +322 -0
- package/skills/router-core/data-loading/SKILL.md +485 -0
- package/skills/router-core/navigation/SKILL.md +448 -0
- package/skills/router-core/not-found-and-errors/SKILL.md +435 -0
- package/skills/router-core/path-params/SKILL.md +382 -0
- package/skills/router-core/search-params/SKILL.md +355 -0
- package/skills/router-core/search-params/references/validation-patterns.md +379 -0
- package/skills/router-core/ssr/SKILL.md +437 -0
- package/skills/router-core/type-safety/SKILL.md +497 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-core/type-safety
|
|
3
|
+
description: >-
|
|
4
|
+
Full type inference philosophy (never cast, never annotate inferred
|
|
5
|
+
values), Register module declaration, from narrowing on hooks and
|
|
6
|
+
Link, strict:false for shared components, getRouteApi for code-split
|
|
7
|
+
typed access, addChildren with object syntax for TS perf, LinkProps
|
|
8
|
+
and ValidateLinkOptions type utilities, as const satisfies pattern.
|
|
9
|
+
type: sub-skill
|
|
10
|
+
library: tanstack-router
|
|
11
|
+
library_version: '1.166.2'
|
|
12
|
+
requires:
|
|
13
|
+
- router-core
|
|
14
|
+
sources:
|
|
15
|
+
- TanStack/router:docs/router/guide/type-safety.md
|
|
16
|
+
- TanStack/router:docs/router/guide/type-utilities.md
|
|
17
|
+
- TanStack/router:docs/router/guide/render-optimizations.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Type Safety
|
|
21
|
+
|
|
22
|
+
TanStack Router is FULLY type-inferred. Params, search params, context, and loader data all flow through the route tree automatically. The **#1 AI agent mistake** is adding type annotations, casts, or generic parameters to values that are already inferred.
|
|
23
|
+
|
|
24
|
+
> **CRITICAL**: NEVER use `as Type`, explicit generic params, `satisfies` on hook returns, or type annotations on inferred values. Every cast masks real type errors and breaks the inference chain.
|
|
25
|
+
> **CRITICAL**: Do NOT confuse TanStack Router with Next.js or React Router. There is no `getServerSideProps`, no `useSearchParams()`, no `useLoaderData()` from `react-router-dom`.
|
|
26
|
+
|
|
27
|
+
## The ONE Required Type Annotation: Register
|
|
28
|
+
|
|
29
|
+
Without this, top-level exports like `Link`, `useNavigate`, `useSearch` have no type safety.
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
// src/router.tsx
|
|
33
|
+
import { createRouter } from '@tanstack/react-router'
|
|
34
|
+
import { routeTree } from './routeTree.gen'
|
|
35
|
+
|
|
36
|
+
const router = createRouter({ routeTree })
|
|
37
|
+
|
|
38
|
+
// THIS IS REQUIRED — the single type registration for the entire app
|
|
39
|
+
declare module '@tanstack/react-router' {
|
|
40
|
+
interface Register {
|
|
41
|
+
router: typeof router
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default router
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
After registration, every `Link`, `useNavigate`, `useSearch`, `useParams` across the app is fully typed.
|
|
49
|
+
|
|
50
|
+
## Types Flow Automatically
|
|
51
|
+
|
|
52
|
+
### Route Hooks — No Annotation Needed
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// src/routes/posts.$postId.tsx
|
|
56
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
57
|
+
|
|
58
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
59
|
+
validateSearch: (search: Record<string, unknown>) => ({
|
|
60
|
+
page: Number(search.page ?? 1),
|
|
61
|
+
}),
|
|
62
|
+
loader: async ({ params }) => {
|
|
63
|
+
// params.postId is already typed as string — do not annotate
|
|
64
|
+
const post = await fetchPost(params.postId)
|
|
65
|
+
return { post }
|
|
66
|
+
},
|
|
67
|
+
component: PostComponent,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
function PostComponent() {
|
|
71
|
+
// ALL of these are fully inferred — do NOT add type annotations
|
|
72
|
+
const { postId } = Route.useParams()
|
|
73
|
+
// ^? string
|
|
74
|
+
|
|
75
|
+
const { page } = Route.useSearch()
|
|
76
|
+
// ^? number
|
|
77
|
+
|
|
78
|
+
const { post } = Route.useLoaderData()
|
|
79
|
+
// ^? { id: string; title: string; body: string }
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div>
|
|
83
|
+
<h1>{post.title}</h1>
|
|
84
|
+
<p>Page {page}</p>
|
|
85
|
+
</div>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Context Flows Through the Tree
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// src/routes/__root.tsx
|
|
94
|
+
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
|
|
95
|
+
|
|
96
|
+
interface RouterContext {
|
|
97
|
+
auth: { userId: string; role: 'admin' | 'user' } | null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Note: createRootRouteWithContext is a FACTORY — call it TWICE: ()()
|
|
101
|
+
export const Route = createRootRouteWithContext<RouterContext>()({
|
|
102
|
+
component: () => <Outlet />,
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
// src/routes/dashboard.tsx
|
|
108
|
+
import { createFileRoute, redirect } from '@tanstack/react-router'
|
|
109
|
+
|
|
110
|
+
export const Route = createFileRoute('/dashboard')({
|
|
111
|
+
beforeLoad: ({ context }) => {
|
|
112
|
+
// context.auth is already typed as { userId: string; role: 'admin' | 'user' } | null
|
|
113
|
+
// NO annotation needed
|
|
114
|
+
if (!context.auth) throw redirect({ to: '/login' })
|
|
115
|
+
return { user: context.auth }
|
|
116
|
+
},
|
|
117
|
+
loader: ({ context }) => {
|
|
118
|
+
// context.user is typed as { userId: string; role: 'admin' | 'user' }
|
|
119
|
+
// This was added by beforeLoad above — fully inferred
|
|
120
|
+
return fetchDashboard(context.user.userId)
|
|
121
|
+
},
|
|
122
|
+
component: DashboardComponent,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
function DashboardComponent() {
|
|
126
|
+
const data = Route.useLoaderData()
|
|
127
|
+
const { user } = Route.useRouteContext()
|
|
128
|
+
return <h1>Welcome {user.userId}</h1>
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Narrowing with `from`
|
|
133
|
+
|
|
134
|
+
Without `from`, hooks return a union of ALL routes' types — slow for TypeScript and imprecise.
|
|
135
|
+
|
|
136
|
+
### On Hooks
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import { useSearch, useParams, useNavigate } from '@tanstack/react-router'
|
|
140
|
+
|
|
141
|
+
function PostSidebar() {
|
|
142
|
+
// WRONG — search is a union of ALL routes' search params
|
|
143
|
+
const search = useSearch()
|
|
144
|
+
|
|
145
|
+
// CORRECT — search is narrowed to /posts/$postId's search params
|
|
146
|
+
const search = useSearch({ from: '/posts/$postId' })
|
|
147
|
+
// ^? { page: number }
|
|
148
|
+
|
|
149
|
+
// CORRECT — params narrowed to this route
|
|
150
|
+
const { postId } = useParams({ from: '/posts/$postId' })
|
|
151
|
+
|
|
152
|
+
// CORRECT — navigate narrowed for relative paths
|
|
153
|
+
const navigate = useNavigate({ from: '/posts/$postId' })
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### On `Link`
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { Link } from '@tanstack/react-router'
|
|
161
|
+
|
|
162
|
+
// WRONG — search resolves to union of ALL routes' search params, slow TS check
|
|
163
|
+
<Link to=".." search={{ page: 0 }} />
|
|
164
|
+
|
|
165
|
+
// CORRECT — narrowed, fast TS check
|
|
166
|
+
<Link from="/posts/$postId" to=".." search={{ page: 0 }} />
|
|
167
|
+
|
|
168
|
+
// Also correct — Route.fullPath in route components
|
|
169
|
+
<Link from={Route.fullPath} to=".." search={{ page: 0 }} />
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Shared Components: `strict: false`
|
|
173
|
+
|
|
174
|
+
When a component is used across multiple routes, use `strict: false` instead of `from`:
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { useSearch } from '@tanstack/react-router'
|
|
178
|
+
|
|
179
|
+
function GlobalSearch() {
|
|
180
|
+
// Returns union of all routes' search params — no runtime error if route doesn't match
|
|
181
|
+
const search = useSearch({ strict: false })
|
|
182
|
+
return <span>Query: {search.q ?? ''}</span>
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Code-Split Files: `getRouteApi`
|
|
187
|
+
|
|
188
|
+
Use `getRouteApi` instead of importing `Route` to avoid pulling route config into the lazy chunk:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
// src/routes/posts.lazy.tsx
|
|
192
|
+
import { createLazyFileRoute, getRouteApi } from '@tanstack/react-router'
|
|
193
|
+
|
|
194
|
+
const routeApi = getRouteApi('/posts')
|
|
195
|
+
|
|
196
|
+
export const Route = createLazyFileRoute('/posts')({
|
|
197
|
+
component: PostsComponent,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
function PostsComponent() {
|
|
201
|
+
const data = routeApi.useLoaderData()
|
|
202
|
+
const { page } = routeApi.useSearch()
|
|
203
|
+
return <div>Page {page}</div>
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## TypeScript Performance
|
|
208
|
+
|
|
209
|
+
### Use Object Syntax for `addChildren` in Large Route Trees
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
// SLOWER — tuple syntax
|
|
213
|
+
const routeTree = rootRoute.addChildren([
|
|
214
|
+
postsRoute.addChildren([postRoute, postsIndexRoute]),
|
|
215
|
+
indexRoute,
|
|
216
|
+
])
|
|
217
|
+
|
|
218
|
+
// FASTER — object syntax (TS checks objects faster than large tuples)
|
|
219
|
+
const routeTree = rootRoute.addChildren({
|
|
220
|
+
postsRoute: postsRoute.addChildren({ postRoute, postsIndexRoute }),
|
|
221
|
+
indexRoute,
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
With file-based routing the route tree is generated, so this is handled for you.
|
|
226
|
+
|
|
227
|
+
### Avoid Returning Unused Inferred Types from Loaders
|
|
228
|
+
|
|
229
|
+
When using external caches like TanStack Query, don't let the router infer complex return types you never consume:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
// SLOWER — TS infers the full ensureQueryData return type into the route tree
|
|
233
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
234
|
+
loader: ({ context: { queryClient }, params: { postId } }) =>
|
|
235
|
+
queryClient.ensureQueryData(postQueryOptions(postId)),
|
|
236
|
+
component: PostComponent,
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// FASTER — void return, inference stays out of the route tree
|
|
240
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
241
|
+
loader: async ({ context: { queryClient }, params: { postId } }) => {
|
|
242
|
+
await queryClient.ensureQueryData(postQueryOptions(postId))
|
|
243
|
+
},
|
|
244
|
+
component: PostComponent,
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### `as const satisfies` for Link Option Objects
|
|
249
|
+
|
|
250
|
+
Never use `LinkProps` as a variable type — it's an enormous union:
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
import type { LinkProps, RegisteredRouter } from '@tanstack/react-router'
|
|
254
|
+
|
|
255
|
+
// WRONG — LinkProps is a massive union, extremely slow TS check
|
|
256
|
+
const wrongProps: LinkProps = { to: '/posts' }
|
|
257
|
+
|
|
258
|
+
// CORRECT — infer a precise type, validate against LinkProps
|
|
259
|
+
const goodProps = { to: '/posts' } as const satisfies LinkProps
|
|
260
|
+
|
|
261
|
+
// EVEN BETTER — narrow LinkProps with generic params
|
|
262
|
+
const narrowedProps = {
|
|
263
|
+
to: '/posts',
|
|
264
|
+
} as const satisfies LinkProps<RegisteredRouter, string, '/posts'>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Type-Safe Link Option Arrays
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
import type { LinkProps } from '@tanstack/react-router'
|
|
271
|
+
|
|
272
|
+
export const navLinks = [
|
|
273
|
+
{ to: '/posts' },
|
|
274
|
+
{ to: '/posts/$postId', params: { postId: '1' } },
|
|
275
|
+
] as const satisfies ReadonlyArray<LinkProps>
|
|
276
|
+
|
|
277
|
+
// Use the precise inferred type, not LinkProps directly
|
|
278
|
+
export type NavLink = (typeof navLinks)[number]
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Type Utilities for Generic Components
|
|
282
|
+
|
|
283
|
+
### `ValidateLinkOptions` — Type-Safe Link Props in Custom Components
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
import {
|
|
287
|
+
Link,
|
|
288
|
+
type RegisteredRouter,
|
|
289
|
+
type ValidateLinkOptions,
|
|
290
|
+
} from '@tanstack/react-router'
|
|
291
|
+
|
|
292
|
+
interface NavItemProps<
|
|
293
|
+
TRouter extends RegisteredRouter = RegisteredRouter,
|
|
294
|
+
TOptions = unknown,
|
|
295
|
+
> {
|
|
296
|
+
label: string
|
|
297
|
+
linkOptions: ValidateLinkOptions<TRouter, TOptions>
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function NavItem<TRouter extends RegisteredRouter, TOptions>(
|
|
301
|
+
props: NavItemProps<TRouter, TOptions>,
|
|
302
|
+
): React.ReactNode
|
|
303
|
+
export function NavItem(props: NavItemProps): React.ReactNode {
|
|
304
|
+
return (
|
|
305
|
+
<li>
|
|
306
|
+
<Link {...props.linkOptions}>{props.label}</Link>
|
|
307
|
+
</li>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Usage — fully type-safe
|
|
312
|
+
<NavItem label="Posts" linkOptions={{ to: '/posts' }} />
|
|
313
|
+
<NavItem label="Post" linkOptions={{ to: '/posts/$postId', params: { postId: '1' } }} />
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### `ValidateNavigateOptions` — Type-Safe Navigate in Utilities
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
import {
|
|
320
|
+
useNavigate,
|
|
321
|
+
type RegisteredRouter,
|
|
322
|
+
type ValidateNavigateOptions,
|
|
323
|
+
} from '@tanstack/react-router'
|
|
324
|
+
|
|
325
|
+
export function useDelayedNavigate<
|
|
326
|
+
TRouter extends RegisteredRouter = RegisteredRouter,
|
|
327
|
+
TOptions = unknown,
|
|
328
|
+
>(
|
|
329
|
+
options: ValidateNavigateOptions<TRouter, TOptions>,
|
|
330
|
+
delayMs: number,
|
|
331
|
+
): () => void
|
|
332
|
+
export function useDelayedNavigate(
|
|
333
|
+
options: ValidateNavigateOptions,
|
|
334
|
+
delayMs: number,
|
|
335
|
+
): () => void {
|
|
336
|
+
const navigate = useNavigate()
|
|
337
|
+
return () => {
|
|
338
|
+
setTimeout(() => navigate(options), delayMs)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Usage — type-safe
|
|
343
|
+
const go = useDelayedNavigate(
|
|
344
|
+
{ to: '/posts/$postId', params: { postId: '1' } },
|
|
345
|
+
500,
|
|
346
|
+
)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### `ValidateRedirectOptions` — Type-Safe Redirect in Utilities
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
import {
|
|
353
|
+
redirect,
|
|
354
|
+
type RegisteredRouter,
|
|
355
|
+
type ValidateRedirectOptions,
|
|
356
|
+
} from '@tanstack/react-router'
|
|
357
|
+
|
|
358
|
+
export async function fetchOrRedirect<
|
|
359
|
+
TRouter extends RegisteredRouter = RegisteredRouter,
|
|
360
|
+
TOptions = unknown,
|
|
361
|
+
>(
|
|
362
|
+
url: string,
|
|
363
|
+
redirectOptions: ValidateRedirectOptions<TRouter, TOptions>,
|
|
364
|
+
): Promise<unknown>
|
|
365
|
+
export async function fetchOrRedirect(
|
|
366
|
+
url: string,
|
|
367
|
+
redirectOptions: ValidateRedirectOptions,
|
|
368
|
+
): Promise<unknown> {
|
|
369
|
+
const response = await fetch(url)
|
|
370
|
+
if (!response.ok && response.status === 401) throw redirect(redirectOptions)
|
|
371
|
+
return response.json()
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Render Props for Maximum Performance
|
|
376
|
+
|
|
377
|
+
Instead of accepting `LinkProps`, invert control so `Link` is narrowed at the call site:
|
|
378
|
+
|
|
379
|
+
```tsx
|
|
380
|
+
function Card(props: { title: string; renderLink: () => React.ReactNode }) {
|
|
381
|
+
return (
|
|
382
|
+
<div>
|
|
383
|
+
<h2>{props.title}</h2>
|
|
384
|
+
{props.renderLink()}
|
|
385
|
+
</div>
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Link narrowed to exactly /posts — no union check
|
|
390
|
+
;<Card title="All Posts" renderLink={() => <Link to="/posts">View</Link>} />
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Render Optimizations
|
|
394
|
+
|
|
395
|
+
### Fine-Grained Selectors with `select`
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
function PostTitle() {
|
|
399
|
+
// Only re-renders when page changes, not when other search params change
|
|
400
|
+
const page = Route.useSearch({ select: ({ page }) => page })
|
|
401
|
+
return <span>Page {page}</span>
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Structural Sharing
|
|
406
|
+
|
|
407
|
+
Preserve referential identity across re-renders for search params:
|
|
408
|
+
|
|
409
|
+
```tsx
|
|
410
|
+
const router = createRouter({
|
|
411
|
+
routeTree,
|
|
412
|
+
defaultStructuralSharing: true, // Enable globally
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
// Or per-hook
|
|
416
|
+
const result = Route.useSearch({
|
|
417
|
+
select: (search) => ({ foo: search.foo, label: `Page ${search.foo}` }),
|
|
418
|
+
structuralSharing: true,
|
|
419
|
+
})
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Structural sharing only works with JSON-compatible data. TypeScript will error if you return class instances with `structuralSharing: true`.
|
|
423
|
+
|
|
424
|
+
## Common Mistakes
|
|
425
|
+
|
|
426
|
+
### 1. CRITICAL: Adding type annotations or casts to inferred values
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
// WRONG — casting masks real type errors
|
|
430
|
+
const search = useSearch({ from: '/posts' }) as { page: number }
|
|
431
|
+
|
|
432
|
+
// WRONG — unnecessary annotation
|
|
433
|
+
const params: { postId: string } = useParams({ from: '/posts/$postId' })
|
|
434
|
+
|
|
435
|
+
// WRONG — generic param on hook
|
|
436
|
+
const data = useLoaderData<{ posts: Post[] }>({ from: '/posts' })
|
|
437
|
+
|
|
438
|
+
// CORRECT — let inference work
|
|
439
|
+
const search = useSearch({ from: '/posts' })
|
|
440
|
+
const params = useParams({ from: '/posts/$postId' })
|
|
441
|
+
const data = useLoaderData({ from: '/posts' })
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### 2. HIGH: Using un-narrowed `LinkProps` type
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
// WRONG — LinkProps is a massive union, causes severe TS slowdown
|
|
448
|
+
const myProps: LinkProps = { to: '/posts' }
|
|
449
|
+
|
|
450
|
+
// CORRECT — use as const satisfies for precise inference
|
|
451
|
+
const myProps = { to: '/posts' } as const satisfies LinkProps
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### 3. HIGH: Not narrowing `Link`/`useNavigate` with `from`
|
|
455
|
+
|
|
456
|
+
```tsx
|
|
457
|
+
// WRONG — search is a union of ALL routes, TS check grows with route count
|
|
458
|
+
<Link to=".." search={{ page: 0 }} />
|
|
459
|
+
|
|
460
|
+
// CORRECT — narrowed, fast check
|
|
461
|
+
<Link from={Route.fullPath} to=".." search={{ page: 0 }} />
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### 4. CRITICAL (cross-skill): Missing router type registration
|
|
465
|
+
|
|
466
|
+
```tsx
|
|
467
|
+
// WRONG — Link/useNavigate have no autocomplete, all paths are untyped strings
|
|
468
|
+
const router = createRouter({ routeTree })
|
|
469
|
+
// (no declare module)
|
|
470
|
+
|
|
471
|
+
// CORRECT — always register
|
|
472
|
+
const router = createRouter({ routeTree })
|
|
473
|
+
declare module '@tanstack/react-router' {
|
|
474
|
+
interface Register {
|
|
475
|
+
router: typeof router
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### 5. CRITICAL (cross-skill): Generating Next.js or Remix patterns
|
|
481
|
+
|
|
482
|
+
```tsx
|
|
483
|
+
// WRONG — these are NOT TanStack Router APIs
|
|
484
|
+
export async function getServerSideProps() { ... }
|
|
485
|
+
export async function loader({ request }) { ... } // Remix-style
|
|
486
|
+
const [searchParams, setSearchParams] = useSearchParams() // React Router
|
|
487
|
+
|
|
488
|
+
// CORRECT — TanStack Router APIs
|
|
489
|
+
export const Route = createFileRoute('/posts')({
|
|
490
|
+
loader: async () => { ... }, // TanStack loader
|
|
491
|
+
validateSearch: zodValidator(schema), // TanStack search validation
|
|
492
|
+
component: PostsComponent,
|
|
493
|
+
})
|
|
494
|
+
const search = Route.useSearch() // TanStack hook
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
See also: router-core (Register setup), router-core/navigation (from narrowing), router-core/code-splitting (getRouteApi).
|