@tanstack/solid-router 1.167.2 → 1.167.4

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 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/solid-router",
3
- "version": "1.167.2",
3
+ "version": "1.167.4",
4
4
  "description": "Modern and scalable routing for Solid applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -78,7 +78,10 @@
78
78
  "sideEffects": false,
79
79
  "files": [
80
80
  "dist",
81
- "src"
81
+ "src",
82
+ "skills",
83
+ "bin",
84
+ "!skills/_artifacts"
82
85
  ],
83
86
  "engines": {
84
87
  "node": ">=20.19"
@@ -92,10 +95,11 @@
92
95
  "tiny-invariant": "^1.3.3",
93
96
  "tiny-warning": "^1.0.3",
94
97
  "@tanstack/history": "1.161.6",
95
- "@tanstack/router-core": "1.167.2"
98
+ "@tanstack/router-core": "1.167.4"
96
99
  },
97
100
  "devDependencies": {
98
101
  "@solidjs/testing-library": "^0.8.10",
102
+ "@tanstack/intent": "^0.0.14",
99
103
  "@testing-library/jest-dom": "^6.6.3",
100
104
  "combinate": "^1.1.11",
101
105
  "eslint-plugin-solid": "^0.14.5",
@@ -107,6 +111,9 @@
107
111
  "peerDependencies": {
108
112
  "solid-js": "^1.9.10"
109
113
  },
114
+ "bin": {
115
+ "intent": "./bin/intent.js"
116
+ },
110
117
  "scripts": {
111
118
  "clean": "rimraf ./dist && rimraf ./coverage",
112
119
  "test:eslint": "eslint",
@@ -0,0 +1,497 @@
1
+ ---
2
+ name: solid-router
3
+ description: >-
4
+ Solid 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. Solid-specific patterns
9
+ with Accessor<T> returns, createSignal/createMemo/createEffect,
10
+ Show/Switch/Match/Dynamic, and @solidjs/meta for head management.
11
+ type: framework
12
+ library: tanstack-router
13
+ library_version: '1.166.2'
14
+ framework: solid
15
+ requires:
16
+ - router-core
17
+ sources:
18
+ - TanStack/router:packages/solid-router/src
19
+ ---
20
+
21
+ # Solid Router (`@tanstack/solid-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 Solid-specific bindings, components, hooks, and setup for TanStack Router.
26
+
27
+ > **CRITICAL**: TanStack Router types are FULLY INFERRED. Never cast, never annotate inferred values.
28
+ > **CRITICAL**: TanStack Router is CLIENT-FIRST. Loaders run on the client by default, not on the server.
29
+ > **CRITICAL**: Most hooks return `Accessor<T>` — you MUST call the accessor (`value()`) to read the reactive value. This is the #1 difference from the React version.
30
+ > **CRITICAL**: Do not confuse `@tanstack/solid-router` with `@solidjs/router`. They are completely different libraries with different APIs.
31
+
32
+ ## Full Setup: File-Based Routing with Vite
33
+
34
+ ### 1. Install Dependencies
35
+
36
+ ```bash
37
+ npm install @tanstack/solid-router
38
+ npm install -D @tanstack/router-plugin @tanstack/solid-router-devtools
39
+ ```
40
+
41
+ ### 2. Configure Vite Plugin
42
+
43
+ ```ts
44
+ // vite.config.ts
45
+ import { defineConfig } from 'vite'
46
+ import solidPlugin from 'vite-plugin-solid'
47
+ import { tanstackRouter } from '@tanstack/router-plugin/vite'
48
+
49
+ export default defineConfig({
50
+ plugins: [
51
+ // MUST come before solid plugin
52
+ tanstackRouter({
53
+ target: 'solid',
54
+ autoCodeSplitting: true,
55
+ }),
56
+ solidPlugin(),
57
+ ],
58
+ })
59
+ ```
60
+
61
+ ### 3. Create Root Route
62
+
63
+ ```tsx
64
+ // src/routes/__root.tsx
65
+ import { createRootRoute, Link, Outlet } from '@tanstack/solid-router'
66
+
67
+ export const Route = createRootRoute({
68
+ component: RootLayout,
69
+ })
70
+
71
+ function RootLayout() {
72
+ return (
73
+ <>
74
+ <nav>
75
+ <Link to="/" activeClass="font-bold">
76
+ Home
77
+ </Link>
78
+ <Link to="/about" activeClass="font-bold">
79
+ About
80
+ </Link>
81
+ </nav>
82
+ <hr />
83
+ <Outlet />
84
+ </>
85
+ )
86
+ }
87
+ ```
88
+
89
+ ### 4. Create Route Files
90
+
91
+ ```tsx
92
+ // src/routes/index.tsx
93
+ import { createFileRoute } from '@tanstack/solid-router'
94
+
95
+ export const Route = createFileRoute('/')({
96
+ component: HomePage,
97
+ })
98
+
99
+ function HomePage() {
100
+ return <h1>Welcome Home</h1>
101
+ }
102
+ ```
103
+
104
+ ### 5. Create Router Instance and Register Types
105
+
106
+ ```tsx
107
+ // src/main.tsx
108
+ import { render } from 'solid-js/web'
109
+ import { RouterProvider, createRouter } from '@tanstack/solid-router'
110
+ import { routeTree } from './routeTree.gen'
111
+
112
+ const router = createRouter({ routeTree })
113
+
114
+ // REQUIRED — without this, Link/useNavigate/useSearch have no type safety
115
+ declare module '@tanstack/solid-router' {
116
+ interface Register {
117
+ router: typeof router
118
+ }
119
+ }
120
+
121
+ render(
122
+ () => <RouterProvider router={router} />,
123
+ document.getElementById('root')!,
124
+ )
125
+ ```
126
+
127
+ ## Hooks Reference
128
+
129
+ All hooks imported from `@tanstack/solid-router`. Most return `Accessor<T>` — call the result to read the value.
130
+
131
+ ### `useRouter()` — returns `TRouter` (NOT an Accessor)
132
+
133
+ ```tsx
134
+ import { useRouter } from '@tanstack/solid-router'
135
+
136
+ function InvalidateButton() {
137
+ const router = useRouter()
138
+ return <button onClick={() => router.invalidate()}>Refresh data</button>
139
+ }
140
+ ```
141
+
142
+ ### `useRouterState()` — returns `Accessor<T>`
143
+
144
+ ```tsx
145
+ import { useRouterState } from '@tanstack/solid-router'
146
+
147
+ function LoadingIndicator() {
148
+ const isLoading = useRouterState({ select: (s) => s.isLoading })
149
+ return (
150
+ <Show when={isLoading()}>
151
+ <div>Loading...</div>
152
+ </Show>
153
+ )
154
+ }
155
+ ```
156
+
157
+ ### `useNavigate()` — returns a function (NOT an Accessor)
158
+
159
+ ```tsx
160
+ import { useNavigate } from '@tanstack/solid-router'
161
+
162
+ function AfterSubmit() {
163
+ const navigate = useNavigate()
164
+
165
+ const handleSubmit = async () => {
166
+ await saveData()
167
+ navigate({ to: '/posts/$postId', params: { postId: '123' } })
168
+ }
169
+
170
+ return <button onClick={handleSubmit}>Save</button>
171
+ }
172
+ ```
173
+
174
+ ### `useSearch({ from })` — returns `Accessor<T>`
175
+
176
+ ```tsx
177
+ import { useSearch } from '@tanstack/solid-router'
178
+
179
+ function Pagination() {
180
+ const search = useSearch({ from: '/products' })
181
+ return <span>Page {search().page}</span>
182
+ }
183
+ ```
184
+
185
+ ### `useParams({ from })` — returns `Accessor<T>`
186
+
187
+ ```tsx
188
+ import { useParams } from '@tanstack/solid-router'
189
+
190
+ function PostHeader() {
191
+ const params = useParams({ from: '/posts/$postId' })
192
+ return <h2>Post {params().postId}</h2>
193
+ }
194
+ ```
195
+
196
+ ### `useLoaderData({ from })` — returns `Accessor<T>`
197
+
198
+ ```tsx
199
+ import { useLoaderData } from '@tanstack/solid-router'
200
+
201
+ function PostContent() {
202
+ const data = useLoaderData({ from: '/posts/$postId' })
203
+ return <article>{data().post.content}</article>
204
+ }
205
+ ```
206
+
207
+ ### `useMatch({ from })` — returns `Accessor<T>`
208
+
209
+ ```tsx
210
+ import { useMatch } from '@tanstack/solid-router'
211
+
212
+ function PostDetails() {
213
+ const match = useMatch({ from: '/posts/$postId' })
214
+ return <div>{match().loaderData.post.title}</div>
215
+ }
216
+ ```
217
+
218
+ ### Other Hooks
219
+
220
+ All imported from `@tanstack/solid-router`:
221
+
222
+ - **`useMatches()`** — `Accessor<Array<Match>>`, all active route matches
223
+ - **`useParentMatches()`** — `Accessor<Array<Match>>`, parent route matches
224
+ - **`useChildMatches()`** — `Accessor<Array<Match>>`, child route matches
225
+ - **`useRouteContext({ from })`** — `Accessor<T>`, context from `beforeLoad`
226
+ - **`useLoaderDeps({ from })`** — `Accessor<T>`, loader dependency values
227
+ - **`useBlocker({ shouldBlockFn })`** — blocks navigation for unsaved changes
228
+ - **`useCanGoBack()`** — `Accessor<boolean>`
229
+ - **`useLocation()`** — `Accessor<ParsedLocation>`
230
+ - **`useLinkProps({ to, params?, search? })`** — returns `ComponentProps<'a'>` (NOT an Accessor)
231
+ - **`useMatchRoute()`** — returns a function; calling it returns `Accessor<false | Params>`
232
+ - **`useHydrated()`** — `Accessor<boolean>`
233
+
234
+ ## Components Reference
235
+
236
+ ### `RouterProvider`
237
+
238
+ ```tsx
239
+ <RouterProvider router={router} />
240
+ ```
241
+
242
+ ### `Link`
243
+
244
+ Type-safe navigation link. Children can be a function for active state:
245
+
246
+ ```tsx
247
+ ;<Link to="/posts/$postId" params={{ postId: '42' }}>
248
+ View Post
249
+ </Link>
250
+
251
+ {
252
+ /* Function children for active state */
253
+ }
254
+ ;<Link to="/about">
255
+ {(state) => <span classList={{ active: state.isActive }}>About</span>}
256
+ </Link>
257
+ ```
258
+
259
+ ### `Outlet`
260
+
261
+ Renders the matched child route component:
262
+
263
+ ```tsx
264
+ function Layout() {
265
+ return (
266
+ <div>
267
+ <Sidebar />
268
+ <main>
269
+ <Outlet />
270
+ </main>
271
+ </div>
272
+ )
273
+ }
274
+ ```
275
+
276
+ ### `Navigate`
277
+
278
+ Declarative redirect (triggers navigation in `onMount`):
279
+
280
+ ```tsx
281
+ import { Navigate } from '@tanstack/solid-router'
282
+
283
+ function OldPage() {
284
+ return <Navigate to="/new-page" />
285
+ }
286
+ ```
287
+
288
+ ### `Await`
289
+
290
+ Renders deferred data with Solid's `Suspense`:
291
+
292
+ ```tsx
293
+ import { Await } from '@tanstack/solid-router'
294
+ import { Suspense } from 'solid-js'
295
+
296
+ function PostWithComments() {
297
+ const data = Route.useLoaderData()
298
+ return (
299
+ <Suspense fallback={<div>Loading...</div>}>
300
+ <Await promise={data().deferredComments}>
301
+ {(comments) => <For each={comments}>{(c) => <li>{c.text}</li>}</For>}
302
+ </Await>
303
+ </Suspense>
304
+ )
305
+ }
306
+ ```
307
+
308
+ ### `CatchBoundary`
309
+
310
+ Error boundary wrapping `Solid.ErrorBoundary`:
311
+
312
+ ```tsx
313
+ import { CatchBoundary } from '@tanstack/solid-router'
314
+ ;<CatchBoundary
315
+ getResetKey={() => 'widget'}
316
+ errorComponent={({ error }) => <div>Error: {error.message}</div>}
317
+ >
318
+ <RiskyWidget />
319
+ </CatchBoundary>
320
+ ```
321
+
322
+ ### Other Components
323
+
324
+ - **`CatchNotFound`** — catches `notFound()` errors in children; `fallback` receives the error data
325
+ - **`Block`** — declarative navigation blocker; use `shouldBlockFn` and `withResolver` for custom UI
326
+ - **`ScrollRestoration`** — **deprecated**; use `createRouter`'s `scrollRestoration: true` option instead
327
+ - **`ClientOnly`** — renders children only after hydration; accepts `fallback` prop
328
+
329
+ ### `Block`
330
+
331
+ Declarative navigation blocker component:
332
+
333
+ ```tsx
334
+ import { Block } from '@tanstack/solid-router'
335
+ ;<Block shouldBlockFn={() => formIsDirty()} withResolver>
336
+ {({ status, proceed, reset }) => (
337
+ <Show when={status === 'blocked'}>
338
+ <div>
339
+ <p>Are you sure?</p>
340
+ <button onClick={proceed}>Yes</button>
341
+ <button onClick={reset}>No</button>
342
+ </div>
343
+ </Show>
344
+ )}
345
+ </Block>
346
+ ```
347
+
348
+ ### `ScrollRestoration`
349
+
350
+ Restores scroll position on navigation:
351
+
352
+ ```tsx
353
+ import { ScrollRestoration } from '@tanstack/solid-router'
354
+ // In root route component
355
+ ;<ScrollRestoration />
356
+ ```
357
+
358
+ ### `ClientOnly`
359
+
360
+ Renders children only after hydration:
361
+
362
+ ```tsx
363
+ import { ClientOnly } from '@tanstack/solid-router'
364
+ ;<ClientOnly fallback={<div>Loading...</div>}>
365
+ <BrowserOnlyWidget />
366
+ </ClientOnly>
367
+ ```
368
+
369
+ ### Head Management
370
+
371
+ Uses `@solidjs/meta` under the hood:
372
+
373
+ ```tsx
374
+ import { HeadContent, Scripts } from '@tanstack/solid-router'
375
+
376
+ function RootDocument(props) {
377
+ return (
378
+ <html>
379
+ <head>
380
+ <HeadContent />
381
+ </head>
382
+ <body>
383
+ {props.children}
384
+ <Scripts />
385
+ </body>
386
+ </html>
387
+ )
388
+ }
389
+ ```
390
+
391
+ ## Solid-Specific Patterns
392
+
393
+ ### Custom Link Component with `createLink`
394
+
395
+ ```tsx
396
+ import { createLink } from '@tanstack/solid-router'
397
+
398
+ const StyledLinkComponent = (props) => (
399
+ <a {...props} class={`styled-link ${props.class ?? ''}`} />
400
+ )
401
+
402
+ const StyledLink = createLink(StyledLinkComponent)
403
+
404
+ function Nav() {
405
+ return (
406
+ <StyledLink to="/posts/$postId" params={{ postId: '42' }}>
407
+ Post
408
+ </StyledLink>
409
+ )
410
+ }
411
+ ```
412
+
413
+ ### Using Solid Primitives with Router State
414
+
415
+ ```tsx
416
+ import { createMemo, Show, For } from 'solid-js'
417
+ import { useRouterState } from '@tanstack/solid-router'
418
+
419
+ function Breadcrumbs() {
420
+ const matches = useRouterState({ select: (s) => s.matches })
421
+ const crumbs = createMemo(() =>
422
+ matches().filter((m) => m.context?.breadcrumb),
423
+ )
424
+
425
+ return (
426
+ <nav>
427
+ <For each={crumbs()}>
428
+ {(match) => <span>{match.context.breadcrumb}</span>}
429
+ </For>
430
+ </nav>
431
+ )
432
+ }
433
+ ```
434
+
435
+ ### Auth with Router Context
436
+
437
+ ```tsx
438
+ import { createRootRouteWithContext } from '@tanstack/solid-router'
439
+
440
+ const rootRoute = createRootRouteWithContext<{ auth: AuthState }>()({
441
+ component: RootComponent,
442
+ })
443
+
444
+ // In main.tsx — provide context at router creation
445
+ const router = createRouter({
446
+ routeTree,
447
+ context: { auth: authState },
448
+ })
449
+
450
+ // In a route — access via beforeLoad (NOT hooks)
451
+ beforeLoad: ({ context }) => {
452
+ if (!context.auth.isAuthenticated) {
453
+ throw redirect({ to: '/login' })
454
+ }
455
+ }
456
+ ```
457
+
458
+ ## Common Mistakes
459
+
460
+ ### 1. CRITICAL: Forgetting to call Accessor
461
+
462
+ Hooks return `Accessor<T>` — you must call them to read the value. This is the #1 migration issue from React.
463
+
464
+ ```tsx
465
+ // WRONG — comparing the accessor function, not its value
466
+ const params = useParams({ from: '/posts/$postId' })
467
+ if (params.postId === '42') { ... } // params is a function!
468
+
469
+ // CORRECT — call the accessor
470
+ const params = useParams({ from: '/posts/$postId' })
471
+ if (params().postId === '42') { ... }
472
+ ```
473
+
474
+ ### 2. HIGH: Destructuring reactive values
475
+
476
+ Destructuring breaks Solid's reactivity tracking.
477
+
478
+ ```tsx
479
+ // WRONG — loses reactivity
480
+ const { page } = useSearch({ from: '/products' })()
481
+
482
+ // CORRECT — access through accessor
483
+ const search = useSearch({ from: '/products' })
484
+ <span>Page {search().page}</span>
485
+ ```
486
+
487
+ ### 3. HIGH: Using React hooks in beforeLoad or loader
488
+
489
+ `beforeLoad` and `loader` are NOT components — they are plain async functions. No hooks (React or Solid) can be used in them. Pass state via router context instead.
490
+
491
+ ### 4. MEDIUM: Wrong plugin target
492
+
493
+ Must set `target: 'solid'` in the router plugin config. Default is `'react'`.
494
+
495
+ ## Cross-References
496
+
497
+ - [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.)