@tanstack/vue-router 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/vue-router/SKILL.md +389 -0
package/bin/intent.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Auto-generated by @tanstack/intent setup
|
|
3
|
+
// Exposes the intent end-user CLI for consumers of this library.
|
|
4
|
+
// Commit this file, then add to your package.json:
|
|
5
|
+
// "bin": { "intent": "./bin/intent.js" }
|
|
6
|
+
try {
|
|
7
|
+
await import('@tanstack/intent/intent-library')
|
|
8
|
+
} catch (e) {
|
|
9
|
+
const isModuleNotFound =
|
|
10
|
+
e?.code === 'ERR_MODULE_NOT_FOUND' || e?.code === 'MODULE_NOT_FOUND'
|
|
11
|
+
const missingIntentLibrary =
|
|
12
|
+
typeof e?.message === 'string' && e.message.includes('@tanstack/intent')
|
|
13
|
+
|
|
14
|
+
if (isModuleNotFound && missingIntentLibrary) {
|
|
15
|
+
console.error('@tanstack/intent is not installed.')
|
|
16
|
+
console.error('')
|
|
17
|
+
console.error('Install it as a dev dependency:')
|
|
18
|
+
console.error(' npm add -D @tanstack/intent')
|
|
19
|
+
console.error('')
|
|
20
|
+
console.error('Or run directly:')
|
|
21
|
+
console.error(' npx @tanstack/intent@latest list')
|
|
22
|
+
process.exit(1)
|
|
23
|
+
}
|
|
24
|
+
throw e
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/vue-router",
|
|
3
|
-
"version": "1.167.
|
|
3
|
+
"version": "1.167.5",
|
|
4
4
|
"description": "Modern and scalable routing for Vue applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,7 +51,10 @@
|
|
|
51
51
|
"sideEffects": false,
|
|
52
52
|
"files": [
|
|
53
53
|
"dist",
|
|
54
|
-
"src"
|
|
54
|
+
"src",
|
|
55
|
+
"skills",
|
|
56
|
+
"bin",
|
|
57
|
+
"!skills/_artifacts"
|
|
55
58
|
],
|
|
56
59
|
"engines": {
|
|
57
60
|
"node": ">=20.19"
|
|
@@ -64,9 +67,10 @@
|
|
|
64
67
|
"tiny-invariant": "^1.3.3",
|
|
65
68
|
"tiny-warning": "^1.0.3",
|
|
66
69
|
"@tanstack/history": "1.161.6",
|
|
67
|
-
"@tanstack/router-core": "1.167.
|
|
70
|
+
"@tanstack/router-core": "1.167.5"
|
|
68
71
|
},
|
|
69
72
|
"devDependencies": {
|
|
73
|
+
"@tanstack/intent": "^0.0.14",
|
|
70
74
|
"@testing-library/jest-dom": "^6.6.3",
|
|
71
75
|
"@testing-library/vue": "^8.1.0",
|
|
72
76
|
"@types/jsesc": "^3.0.3",
|
|
@@ -80,6 +84,9 @@
|
|
|
80
84
|
"peerDependencies": {
|
|
81
85
|
"vue": "^3.3.0"
|
|
82
86
|
},
|
|
87
|
+
"bin": {
|
|
88
|
+
"intent": "./bin/intent.js"
|
|
89
|
+
},
|
|
83
90
|
"scripts": {
|
|
84
91
|
"clean": "rimraf ./dist && rimraf ./coverage",
|
|
85
92
|
"test": "pnpm run test:unit",
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vue-router
|
|
3
|
+
description: >-
|
|
4
|
+
Vue bindings for TanStack Router: RouterProvider, useRouter,
|
|
5
|
+
useRouterState, useMatch, useMatches, useLocation, useSearch,
|
|
6
|
+
useParams, useNavigate, useLoaderData, useLoaderDeps,
|
|
7
|
+
useRouteContext, useBlocker, useCanGoBack, Link, Navigate,
|
|
8
|
+
Outlet, CatchBoundary, ErrorComponent, Html, Body.
|
|
9
|
+
Vue-specific patterns with Ref<T> returns, defineComponent,
|
|
10
|
+
h() render functions, provide/inject, and computed refs.
|
|
11
|
+
type: framework
|
|
12
|
+
library: tanstack-router
|
|
13
|
+
library_version: '1.166.2'
|
|
14
|
+
framework: vue
|
|
15
|
+
requires:
|
|
16
|
+
- router-core
|
|
17
|
+
sources:
|
|
18
|
+
- TanStack/router:packages/vue-router/src
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Vue Router (`@tanstack/vue-router`)
|
|
22
|
+
|
|
23
|
+
This skill builds on router-core. Read [router-core](../../../router-core/skills/router-core/SKILL.md) first for foundational concepts.
|
|
24
|
+
|
|
25
|
+
This skill covers the Vue-specific bindings, components, composables, and setup for TanStack Router.
|
|
26
|
+
|
|
27
|
+
> **CRITICAL**: TanStack Router types are FULLY INFERRED. Never cast, never annotate inferred values.
|
|
28
|
+
|
|
29
|
+
> **CRITICAL**: TanStack Router is CLIENT-FIRST. Loaders run on the client by default, not on the server.
|
|
30
|
+
|
|
31
|
+
> **CRITICAL**: Most composables return `Ref<T>` — access via `.value` in script, auto-unwrapped in templates. This is the #1 difference from the React version.
|
|
32
|
+
|
|
33
|
+
> **CRITICAL**: Do not confuse `@tanstack/vue-router` with `vue-router` (the official Vue router). They are completely different libraries with different APIs.
|
|
34
|
+
|
|
35
|
+
## Full Setup: File-Based Routing with Vite
|
|
36
|
+
|
|
37
|
+
### 1. Install Dependencies
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @tanstack/vue-router
|
|
41
|
+
npm install -D @tanstack/router-plugin @vitejs/plugin-vue-jsx
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Configure Vite Plugin
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// vite.config.ts
|
|
48
|
+
import { defineConfig } from 'vite'
|
|
49
|
+
import vue from '@vitejs/plugin-vue'
|
|
50
|
+
import vueJsx from '@vitejs/plugin-vue-jsx'
|
|
51
|
+
import { tanstackRouter } from '@tanstack/router-plugin/vite'
|
|
52
|
+
|
|
53
|
+
export default defineConfig({
|
|
54
|
+
plugins: [
|
|
55
|
+
// MUST come before vue()
|
|
56
|
+
tanstackRouter({
|
|
57
|
+
target: 'vue',
|
|
58
|
+
autoCodeSplitting: true,
|
|
59
|
+
}),
|
|
60
|
+
vue(),
|
|
61
|
+
vueJsx(), // Required for JSX/TSX route files
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. Create Root Route
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// src/routes/__root.tsx
|
|
70
|
+
import { createRootRoute, Link, Outlet } from '@tanstack/vue-router'
|
|
71
|
+
|
|
72
|
+
export const Route = createRootRoute({
|
|
73
|
+
component: RootLayout,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
function RootLayout() {
|
|
77
|
+
return (
|
|
78
|
+
<>
|
|
79
|
+
<nav>
|
|
80
|
+
<Link to="/" activeProps={{ class: 'font-bold' }}>
|
|
81
|
+
Home
|
|
82
|
+
</Link>
|
|
83
|
+
<Link to="/about" activeProps={{ class: 'font-bold' }}>
|
|
84
|
+
About
|
|
85
|
+
</Link>
|
|
86
|
+
</nav>
|
|
87
|
+
<hr />
|
|
88
|
+
<Outlet />
|
|
89
|
+
</>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4. Create Route Files
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
// src/routes/index.tsx
|
|
98
|
+
import { createFileRoute } from '@tanstack/vue-router'
|
|
99
|
+
|
|
100
|
+
export const Route = createFileRoute('/')({
|
|
101
|
+
component: HomePage,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
function HomePage() {
|
|
105
|
+
return <h1>Welcome Home</h1>
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 5. Create Router Instance and Register Types
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// src/main.tsx
|
|
113
|
+
import { createApp } from 'vue'
|
|
114
|
+
import { RouterProvider, createRouter } from '@tanstack/vue-router'
|
|
115
|
+
import { routeTree } from './routeTree.gen'
|
|
116
|
+
|
|
117
|
+
const router = createRouter({ routeTree })
|
|
118
|
+
|
|
119
|
+
// REQUIRED — without this, Link/useNavigate/useSearch have no type safety
|
|
120
|
+
declare module '@tanstack/vue-router' {
|
|
121
|
+
interface Register {
|
|
122
|
+
router: typeof router
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const app = createApp(RouterProvider, { router })
|
|
127
|
+
app.mount('#root')
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Composables Reference
|
|
131
|
+
|
|
132
|
+
All composables imported from `@tanstack/vue-router`. Most return `Ref<T>` — access via `.value` in script or auto-unwrap in templates.
|
|
133
|
+
|
|
134
|
+
### `useRouter()` — returns `TRouter` (NOT a Ref)
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { useRouter } from '@tanstack/vue-router'
|
|
138
|
+
|
|
139
|
+
const router = useRouter()
|
|
140
|
+
router.invalidate()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### `useRouterState()` — returns `Ref<T>`
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { useRouterState } from '@tanstack/vue-router'
|
|
147
|
+
|
|
148
|
+
const isLoading = useRouterState({ select: (s) => s.isLoading })
|
|
149
|
+
// Access: isLoading.value
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `useNavigate()` — returns a function (NOT a Ref)
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
import { useNavigate } from '@tanstack/vue-router'
|
|
156
|
+
|
|
157
|
+
const navigate = useNavigate()
|
|
158
|
+
|
|
159
|
+
async function handleSubmit() {
|
|
160
|
+
await saveData()
|
|
161
|
+
navigate({ to: '/posts/$postId', params: { postId: '123' } })
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `useSearch({ from })` — returns `Ref<T>`
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { useSearch } from '@tanstack/vue-router'
|
|
169
|
+
|
|
170
|
+
const search = useSearch({ from: '/products' })
|
|
171
|
+
// Access: search.value.page
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### `useParams({ from })` — returns `Ref<T>`
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { useParams } from '@tanstack/vue-router'
|
|
178
|
+
|
|
179
|
+
const params = useParams({ from: '/posts/$postId' })
|
|
180
|
+
// Access: params.value.postId
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### `useLoaderData({ from })` — returns `Ref<T>`
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { useLoaderData } from '@tanstack/vue-router'
|
|
187
|
+
|
|
188
|
+
const data = useLoaderData({ from: '/posts/$postId' })
|
|
189
|
+
// Access: data.value.post.content
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `useMatch({ from })` — returns `Ref<T>`
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import { useMatch } from '@tanstack/vue-router'
|
|
196
|
+
|
|
197
|
+
const match = useMatch({ from: '/posts/$postId' })
|
|
198
|
+
// Access: match.value.loaderData.post.title
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Other Composables
|
|
202
|
+
|
|
203
|
+
- **`useMatches()`** — `Ref<Array<Match>>`, all active route matches
|
|
204
|
+
- **`useRouteContext({ from })`** — `Ref<T>`, context from `beforeLoad`
|
|
205
|
+
- **`useBlocker({ shouldBlockFn })`** — blocks navigation for unsaved changes
|
|
206
|
+
- **`useCanGoBack()`** — `Ref<boolean>`
|
|
207
|
+
- **`useLocation()`** — `Ref<ParsedLocation>`
|
|
208
|
+
- **`useLoaderDeps({ from })`** — `Ref<T>`, loader dependency values
|
|
209
|
+
- **`useLinkProps()`** — returns `LinkHTMLAttributes`
|
|
210
|
+
- **`useMatchRoute()`** — returns a function; calling it returns `Ref<false | Params>`
|
|
211
|
+
|
|
212
|
+
## Components Reference
|
|
213
|
+
|
|
214
|
+
### `RouterProvider`
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { RouterProvider } from '@tanstack/vue-router'
|
|
218
|
+
// In createApp or template
|
|
219
|
+
<RouterProvider :router="router" />
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### `Link`
|
|
223
|
+
|
|
224
|
+
Type-safe navigation link with scoped slot for active state:
|
|
225
|
+
|
|
226
|
+
```vue
|
|
227
|
+
<Link to="/posts/$postId" :params="{ postId: '42' }">
|
|
228
|
+
View Post
|
|
229
|
+
</Link>
|
|
230
|
+
|
|
231
|
+
<!-- Scoped slot for active state -->
|
|
232
|
+
<Link to="/about">
|
|
233
|
+
<template #default="{ isActive }">
|
|
234
|
+
<span :class="{ active: isActive }">About</span>
|
|
235
|
+
</template>
|
|
236
|
+
</Link>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### `Outlet`
|
|
240
|
+
|
|
241
|
+
Renders the matched child route component.
|
|
242
|
+
|
|
243
|
+
### `Navigate`
|
|
244
|
+
|
|
245
|
+
Declarative redirect (triggers navigation in `onMounted`).
|
|
246
|
+
|
|
247
|
+
### `Await`
|
|
248
|
+
|
|
249
|
+
Async setup component for deferred data — use with Vue's `<Suspense>`.
|
|
250
|
+
|
|
251
|
+
### `CatchBoundary`
|
|
252
|
+
|
|
253
|
+
Error boundary using Vue's `onErrorCaptured`.
|
|
254
|
+
|
|
255
|
+
### `Html` and `Body`
|
|
256
|
+
|
|
257
|
+
Vue-specific SSR shell components:
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
function RootComponent() {
|
|
261
|
+
return (
|
|
262
|
+
<Html>
|
|
263
|
+
<head>
|
|
264
|
+
<HeadContent />
|
|
265
|
+
</head>
|
|
266
|
+
<Body>
|
|
267
|
+
<Outlet />
|
|
268
|
+
<Scripts />
|
|
269
|
+
</Body>
|
|
270
|
+
</Html>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### `ClientOnly`
|
|
276
|
+
|
|
277
|
+
Renders children only after `onMounted` (hydration complete):
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
<ClientOnly fallback={<div>Loading...</div>}>
|
|
281
|
+
<BrowserOnlyWidget />
|
|
282
|
+
</ClientOnly>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Vue-Specific Patterns
|
|
286
|
+
|
|
287
|
+
### Custom Link Component with `createLink`
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
import { createLink } from '@tanstack/vue-router'
|
|
291
|
+
import { defineComponent, h } from 'vue'
|
|
292
|
+
|
|
293
|
+
const StyledLinkComponent = defineComponent({
|
|
294
|
+
setup(props, { slots, attrs }) {
|
|
295
|
+
return () => h('a', { ...attrs, class: 'styled-link' }, slots.default?.())
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
const StyledLink = createLink(StyledLinkComponent)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Render Functions (h())
|
|
303
|
+
|
|
304
|
+
All components in `@tanstack/vue-router` use `h()` render functions internally. Route components can use either SFC templates or render functions:
|
|
305
|
+
|
|
306
|
+
SFC template (most common for user code) in `MyRoute.component.vue`:
|
|
307
|
+
|
|
308
|
+
```vue
|
|
309
|
+
<template>
|
|
310
|
+
<div>{{ data.title }}</div>
|
|
311
|
+
</template>
|
|
312
|
+
|
|
313
|
+
<script setup>
|
|
314
|
+
import { useLoaderData } from '@tanstack/vue-router'
|
|
315
|
+
const data = useLoaderData({ from: '/posts/$postId' })
|
|
316
|
+
</script>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Auth with Router Context
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
import { createRootRouteWithContext } from '@tanstack/vue-router'
|
|
323
|
+
|
|
324
|
+
const rootRoute = createRootRouteWithContext<{ auth: AuthState }>()({
|
|
325
|
+
component: RootComponent,
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
const router = createRouter({
|
|
329
|
+
routeTree,
|
|
330
|
+
context: { auth: authState },
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
// In a route — access via beforeLoad
|
|
334
|
+
beforeLoad: ({ context }) => {
|
|
335
|
+
if (!context.auth.isAuthenticated) {
|
|
336
|
+
throw redirect({ to: '/login' })
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Vue File Conventions for Code Splitting
|
|
342
|
+
|
|
343
|
+
With `autoCodeSplitting`, Vue routes can optionally use split-file conventions. These are NOT required — single-file `.tsx` routes work fine. Split files are useful for separating route config from components:
|
|
344
|
+
|
|
345
|
+
- `myRoute.ts` — route configuration (search params, loader, beforeLoad)
|
|
346
|
+
- `myRoute.component.vue` — route component (lazy-loaded)
|
|
347
|
+
- `myRoute.errorComponent.vue` — error component (lazy-loaded)
|
|
348
|
+
- `myRoute.notFoundComponent.vue` — not-found component (lazy-loaded)
|
|
349
|
+
- `myRoute.lazy.ts` — lazy-loaded route options
|
|
350
|
+
|
|
351
|
+
## Common Mistakes
|
|
352
|
+
|
|
353
|
+
### 1. CRITICAL: Forgetting .value in script blocks
|
|
354
|
+
|
|
355
|
+
Composables return `Ref<T>` — access via `.value` in `<script>`. Templates auto-unwrap.
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
// WRONG — accessing Ref without .value in script
|
|
359
|
+
const params = useParams({ from: '/posts/$postId' })
|
|
360
|
+
console.log(params.postId) // undefined!
|
|
361
|
+
|
|
362
|
+
// CORRECT — use .value
|
|
363
|
+
const params = useParams({ from: '/posts/$postId' })
|
|
364
|
+
console.log(params.value.postId)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### 2. HIGH: Confusing with vue-router (official)
|
|
368
|
+
|
|
369
|
+
`@tanstack/vue-router` is NOT `vue-router`. Do not use `<router-view>`, `<router-link>`, `useRoute()`, `useRouter()` from `vue-router`.
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
// WRONG — official vue-router imports
|
|
373
|
+
import { useRoute, useRouter } from 'vue-router'
|
|
374
|
+
|
|
375
|
+
// CORRECT — TanStack Vue Router imports
|
|
376
|
+
import { useMatch, useRouter } from '@tanstack/vue-router'
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### 3. HIGH: Using Vue hooks in beforeLoad or loader
|
|
380
|
+
|
|
381
|
+
`beforeLoad` and `loader` are NOT component setup functions — they are plain async functions. Vue composables cannot be used in them. Pass state via router context instead.
|
|
382
|
+
|
|
383
|
+
### 4. MEDIUM: Wrong plugin target
|
|
384
|
+
|
|
385
|
+
Must set `target: 'vue'` in the router plugin config. Default is `'react'`.
|
|
386
|
+
|
|
387
|
+
## Cross-References
|
|
388
|
+
|
|
389
|
+
- [router-core/SKILL.md](../../../router-core/skills/router-core/SKILL.md) — all sub-skills for domain-specific patterns (search params, data loading, navigation, auth, SSR, etc.)
|