@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.
@@ -0,0 +1,448 @@
1
+ ---
2
+ name: router-core/navigation
3
+ description: >-
4
+ Link component, useNavigate, Navigate component, router.navigate,
5
+ ToOptions/NavigateOptions/LinkOptions, from/to relative navigation,
6
+ activeOptions/activeProps, preloading (intent/viewport/render),
7
+ preloadDelay, navigation blocking (useBlocker, Block), createLink,
8
+ linkOptions helper, scroll restoration, MatchRoute.
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/navigation.md
16
+ - TanStack/router:docs/router/guide/preloading.md
17
+ - TanStack/router:docs/router/guide/navigation-blocking.md
18
+ - TanStack/router:docs/router/guide/link-options.md
19
+ - TanStack/router:docs/router/guide/custom-link.md
20
+ - TanStack/router:docs/router/guide/scroll-restoration.md
21
+ ---
22
+
23
+ # Navigation
24
+
25
+ ## Setup
26
+
27
+ Basic type-safe `Link` with `to` and `params`:
28
+
29
+ ```tsx
30
+ import { Link } from '@tanstack/react-router'
31
+
32
+ function PostLink({ postId }: { postId: string }) {
33
+ return (
34
+ <Link to="/posts/$postId" params={{ postId }}>
35
+ View Post
36
+ </Link>
37
+ )
38
+ }
39
+ ```
40
+
41
+ ## Core Patterns
42
+
43
+ ### Link with Active States
44
+
45
+ ```tsx
46
+ import { Link } from '@tanstack/react-router'
47
+
48
+ function NavLink() {
49
+ return (
50
+ <Link
51
+ to="/posts"
52
+ activeProps={{ className: 'font-bold' }}
53
+ inactiveProps={{ className: 'text-gray-500' }}
54
+ activeOptions={{ exact: true }}
55
+ >
56
+ Posts
57
+ </Link>
58
+ )
59
+ }
60
+ ```
61
+
62
+ The `data-status` attribute is also set to `"active"` on active links for CSS-based styling.
63
+
64
+ `activeOptions` controls matching behavior:
65
+
66
+ - `exact` (default `false`) — when `true`, only matches the exact path (not children)
67
+ - `includeHash` (default `false`) — include hash in active matching
68
+ - `includeSearch` (default `true`) — include search params in active matching
69
+
70
+ Children can receive `isActive` as a render function:
71
+
72
+ ```tsx
73
+ <Link to="/posts">
74
+ {({ isActive }) => <span className={isActive ? 'font-bold' : ''}>Posts</span>}
75
+ </Link>
76
+ ```
77
+
78
+ ### Relative Navigation with `from`
79
+
80
+ Without `from`, navigation resolves from root `/`. To use relative paths like `..`, provide `from`:
81
+
82
+ ```tsx
83
+ import { createFileRoute, Link } from '@tanstack/react-router'
84
+
85
+ export const Route = createFileRoute('/posts/$postId')({
86
+ component: PostComponent,
87
+ })
88
+
89
+ function PostComponent() {
90
+ return (
91
+ <div>
92
+ {/* Relative to current route */}
93
+ <Link from={Route.fullPath} to="..">
94
+ Back to Posts
95
+ </Link>
96
+
97
+ {/* "." reloads the current route */}
98
+ <Link from={Route.fullPath} to=".">
99
+ Reload
100
+ </Link>
101
+ </div>
102
+ )
103
+ }
104
+ ```
105
+
106
+ ### useNavigate for Programmatic Navigation
107
+
108
+ Use `useNavigate` only for side-effect-driven navigation (e.g., after a form submission). For anything the user clicks, prefer `Link`.
109
+
110
+ ```tsx
111
+ import { useNavigate } from '@tanstack/react-router'
112
+
113
+ function CreatePostForm() {
114
+ const navigate = useNavigate({ from: '/posts' })
115
+
116
+ const handleSubmit = async (e: React.FormEvent) => {
117
+ e.preventDefault()
118
+ const response = await fetch('/api/posts', { method: 'POST', body: '...' })
119
+ const { id: postId } = await response.json()
120
+
121
+ if (response.ok) {
122
+ navigate({ to: '/posts/$postId', params: { postId } })
123
+ }
124
+ }
125
+
126
+ return <form onSubmit={handleSubmit}>{/* ... */}</form>
127
+ }
128
+ ```
129
+
130
+ The `Navigate` component performs an immediate client-side navigation on mount:
131
+
132
+ ```tsx
133
+ import { Navigate } from '@tanstack/react-router'
134
+
135
+ function LegacyRedirect() {
136
+ return <Navigate to="/posts/$postId" params={{ postId: 'my-first-post' }} />
137
+ }
138
+ ```
139
+
140
+ `router.navigate` is available anywhere you have the router instance, including outside of React.
141
+
142
+ ### Preloading
143
+
144
+ Strategies: `intent` (hover/touchstart), `viewport` (intersection observer), `render` (on mount).
145
+
146
+ Set globally:
147
+
148
+ ```tsx
149
+ import { createRouter } from '@tanstack/react-router'
150
+
151
+ const router = createRouter({
152
+ routeTree,
153
+ defaultPreload: 'intent',
154
+ defaultPreloadDelay: 50, // ms, default is 50
155
+ })
156
+ ```
157
+
158
+ Or per-link:
159
+
160
+ ```tsx
161
+ <Link
162
+ to="/posts/$postId"
163
+ params={{ postId }}
164
+ preload="intent"
165
+ preloadDelay={100}
166
+ >
167
+ View Post
168
+ </Link>
169
+ ```
170
+
171
+ Preloaded data stays fresh for 30 seconds by default (`defaultPreloadStaleTime: 30_000`). During that window it won't be refetched. When using an external cache like TanStack Query, set `defaultPreloadStaleTime: 0` to let the external library control freshness.
172
+
173
+ Manual preloading via the router instance:
174
+
175
+ ```tsx
176
+ import { useRouter } from '@tanstack/react-router'
177
+
178
+ function Component() {
179
+ const router = useRouter()
180
+
181
+ useEffect(() => {
182
+ router.preloadRoute({ to: '/posts/$postId', params: { postId: '1' } })
183
+ }, [router])
184
+
185
+ return <div />
186
+ }
187
+ ```
188
+
189
+ ### Navigation Blocking
190
+
191
+ Use `useBlocker` to prevent navigation when a form has unsaved changes:
192
+
193
+ ```tsx
194
+ import { useBlocker } from '@tanstack/react-router'
195
+ import { useState } from 'react'
196
+
197
+ function EditForm() {
198
+ const [formIsDirty, setFormIsDirty] = useState(false)
199
+
200
+ useBlocker({
201
+ shouldBlockFn: () => {
202
+ if (!formIsDirty) return false
203
+ const shouldLeave = confirm('Are you sure you want to leave?')
204
+ return !shouldLeave
205
+ },
206
+ })
207
+
208
+ return <form>{/* ... */}</form>
209
+ }
210
+ ```
211
+
212
+ With custom UI using `withResolver`:
213
+
214
+ ```tsx
215
+ import { useBlocker } from '@tanstack/react-router'
216
+ import { useState } from 'react'
217
+
218
+ function EditForm() {
219
+ const [formIsDirty, setFormIsDirty] = useState(false)
220
+
221
+ const { proceed, reset, status } = useBlocker({
222
+ shouldBlockFn: () => formIsDirty,
223
+ withResolver: true,
224
+ })
225
+
226
+ return (
227
+ <>
228
+ <form>{/* ... */}</form>
229
+ {status === 'blocked' && (
230
+ <div>
231
+ <p>Are you sure you want to leave?</p>
232
+ <button onClick={proceed}>Yes</button>
233
+ <button onClick={reset}>No</button>
234
+ </div>
235
+ )}
236
+ </>
237
+ )
238
+ }
239
+ ```
240
+
241
+ Control `beforeunload` separately:
242
+
243
+ ```tsx
244
+ useBlocker({
245
+ shouldBlockFn: () => formIsDirty,
246
+ enableBeforeUnload: formIsDirty,
247
+ })
248
+ ```
249
+
250
+ ### linkOptions for Reusable Navigation Options
251
+
252
+ `linkOptions` provides eager type-checking on navigation options objects, so errors surface at definition, not at spread-site:
253
+
254
+ ```tsx
255
+ import {
256
+ linkOptions,
257
+ Link,
258
+ useNavigate,
259
+ redirect,
260
+ } from '@tanstack/react-router'
261
+
262
+ const dashboardLinkOptions = linkOptions({
263
+ to: '/dashboard',
264
+ search: { search: '' },
265
+ })
266
+
267
+ // Use anywhere: Link, navigate, redirect
268
+ function Nav() {
269
+ const navigate = useNavigate()
270
+
271
+ return (
272
+ <div>
273
+ <Link {...dashboardLinkOptions}>Dashboard</Link>
274
+ <button onClick={() => navigate(dashboardLinkOptions)}>Go</button>
275
+ </div>
276
+ )
277
+ }
278
+
279
+ // Also works in an array for navigation bars
280
+ const navOptions = linkOptions([
281
+ { to: '/dashboard', label: 'Summary', activeOptions: { exact: true } },
282
+ { to: '/dashboard/invoices', label: 'Invoices' },
283
+ { to: '/dashboard/users', label: 'Users' },
284
+ ])
285
+
286
+ function NavBar() {
287
+ return (
288
+ <nav>
289
+ {navOptions.map((option) => (
290
+ <Link
291
+ {...option}
292
+ key={option.to}
293
+ activeProps={{ className: 'font-bold' }}
294
+ >
295
+ {option.label}
296
+ </Link>
297
+ ))}
298
+ </nav>
299
+ )
300
+ }
301
+ ```
302
+
303
+ ### createLink for Custom Components
304
+
305
+ Wraps any component with TanStack Router's type-safe navigation:
306
+
307
+ ```tsx
308
+ import * as React from 'react'
309
+ import { createLink, LinkComponent } from '@tanstack/react-router'
310
+
311
+ interface BasicLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {}
312
+
313
+ const BasicLinkComponent = React.forwardRef<HTMLAnchorElement, BasicLinkProps>(
314
+ (props, ref) => {
315
+ return <a ref={ref} {...props} className="block px-3 py-2 text-blue-700" />
316
+ },
317
+ )
318
+
319
+ const CreatedLinkComponent = createLink(BasicLinkComponent)
320
+
321
+ export const CustomLink: LinkComponent<typeof BasicLinkComponent> = (props) => {
322
+ return <CreatedLinkComponent preload="intent" {...props} />
323
+ }
324
+ ```
325
+
326
+ Usage retains full type safety:
327
+
328
+ ```tsx
329
+ <CustomLink to="/dashboard/invoices/$invoiceId" params={{ invoiceId: 0 }} />
330
+ ```
331
+
332
+ ### Scroll Restoration
333
+
334
+ Enable globally on the router:
335
+
336
+ ```tsx
337
+ const router = createRouter({
338
+ routeTree,
339
+ scrollRestoration: true,
340
+ })
341
+ ```
342
+
343
+ For nested scrollable areas:
344
+
345
+ ```tsx
346
+ const router = createRouter({
347
+ routeTree,
348
+ scrollRestoration: true,
349
+ scrollToTopSelectors: ['#main-scrollable-area'],
350
+ })
351
+ ```
352
+
353
+ Custom cache keys:
354
+
355
+ ```tsx
356
+ const router = createRouter({
357
+ routeTree,
358
+ scrollRestoration: true,
359
+ getScrollRestorationKey: (location) => location.pathname,
360
+ })
361
+ ```
362
+
363
+ Prevent scroll reset for a specific navigation:
364
+
365
+ ```tsx
366
+ <Link to="/posts" resetScroll={false}>
367
+ Posts
368
+ </Link>
369
+ ```
370
+
371
+ ### MatchRoute for Pending UI
372
+
373
+ ```tsx
374
+ import { Link, MatchRoute } from '@tanstack/react-router'
375
+
376
+ function Nav() {
377
+ return (
378
+ <Link to="/users">
379
+ Users
380
+ <MatchRoute to="/users" pending>
381
+ <Spinner />
382
+ </MatchRoute>
383
+ </Link>
384
+ )
385
+ }
386
+ ```
387
+
388
+ ## Common Mistakes
389
+
390
+ ### CRITICAL: Interpolating params into the `to` string
391
+
392
+ ```tsx
393
+ // WRONG — breaks type safety and param encoding
394
+ <Link to={`/posts/${postId}`}>Post</Link>
395
+
396
+ // CORRECT — use the params option
397
+ <Link to="/posts/$postId" params={{ postId }}>Post</Link>
398
+ ```
399
+
400
+ Dynamic segments are declared with `$` in the route path. Always pass them via `params`. This applies to `Link`, `useNavigate`, `Navigate`, and `router.navigate`.
401
+
402
+ ### MEDIUM: Using useNavigate for clickable elements
403
+
404
+ ```tsx
405
+ // WRONG — no href, no cmd+click, no preloading, no accessibility
406
+ function BadNav() {
407
+ const navigate = useNavigate()
408
+ return <button onClick={() => navigate({ to: '/posts' })}>Posts</button>
409
+ }
410
+
411
+ // CORRECT — real <a> tag with href, accessible, preloadable
412
+ function GoodNav() {
413
+ return <Link to="/posts">Posts</Link>
414
+ }
415
+ ```
416
+
417
+ Use `useNavigate` only for programmatic side-effect navigation (after form submit, async action, etc).
418
+
419
+ ### HIGH: Not providing `from` for relative navigation
420
+
421
+ ```tsx
422
+ // WRONG — without from, ".." resolves from root
423
+ <Link to="..">Back</Link>
424
+
425
+ // CORRECT — provide from for relative resolution
426
+ <Link from={Route.fullPath} to="..">Back</Link>
427
+ ```
428
+
429
+ Without `from`, only absolute paths are autocompleted and type-safe. Relative paths like `..` resolve from root instead of the current route.
430
+
431
+ ### HIGH: Using search as object instead of function loses existing params
432
+
433
+ ```tsx
434
+ // WRONG — replaces ALL search params with just { page: 2 }
435
+ <Link to="." search={{ page: 2 }}>Page 2</Link>
436
+
437
+ // CORRECT — preserves existing search params, updates page
438
+ <Link to="." search={(prev) => ({ ...prev, page: 2 })}>Page 2</Link>
439
+ ```
440
+
441
+ When you pass `search` as a plain object, it replaces all search params. Use the function form to spread previous params and selectively update.
442
+
443
+ ---
444
+
445
+ ## Cross-References
446
+
447
+ - See also: **router-core/search-params/SKILL.md** — Link `search` prop interacts with search param validation
448
+ - See also: **router-core/type-safety/SKILL.md** — `from` narrowing improves type inference on Link