@solidjs/router 1.0.0 → 2.0.0-next.13

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.
Files changed (56) hide show
  1. package/README.md +606 -697
  2. package/dist/claims.d.ts +21 -0
  3. package/dist/claims.js +115 -0
  4. package/dist/data/action.d.ts +40 -11
  5. package/dist/data/action.js +307 -101
  6. package/dist/data/events.d.ts +8 -0
  7. package/dist/data/events.js +24 -23
  8. package/dist/data/index.d.ts +2 -4
  9. package/dist/data/index.js +2 -4
  10. package/dist/data/query.d.ts +1 -3
  11. package/dist/data/query.js +75 -32
  12. package/dist/data/serverForms.d.ts +1 -0
  13. package/dist/data/serverForms.js +5 -0
  14. package/dist/fs.d.ts +94 -0
  15. package/dist/fs.js +51 -0
  16. package/dist/index.d.ts +24 -3
  17. package/dist/index.js +1949 -1204
  18. package/dist/index.jsx +2 -2
  19. package/dist/lifecycle.d.ts +29 -4
  20. package/dist/lifecycle.js +40 -37
  21. package/dist/paths.d.ts +117 -0
  22. package/dist/paths.js +41 -0
  23. package/dist/routers/components.d.ts +12 -26
  24. package/dist/routers/components.jsx +68 -54
  25. package/dist/routers/factory.d.ts +121 -0
  26. package/dist/routers/factory.jsx +154 -0
  27. package/dist/routers/history.d.ts +26 -0
  28. package/dist/routers/history.js +180 -0
  29. package/dist/routers/index.d.ts +5 -11
  30. package/dist/routers/index.js +2 -6
  31. package/dist/routers/scrollRestoration.d.ts +10 -1
  32. package/dist/routers/scrollRestoration.js +49 -11
  33. package/dist/routing.d.ts +96 -52
  34. package/dist/routing.js +429 -172
  35. package/dist/server.d.ts +47 -0
  36. package/dist/server.js +156 -0
  37. package/dist/types.d.ts +158 -44
  38. package/dist/utils.d.ts +2 -0
  39. package/dist/utils.js +2 -0
  40. package/package.json +12 -7
  41. package/dist/components.d.ts +0 -31
  42. package/dist/components.jsx +0 -40
  43. package/dist/data/createAsync.d.ts +0 -32
  44. package/dist/data/createAsync.js +0 -96
  45. package/dist/data/response.d.ts +0 -4
  46. package/dist/data/response.js +0 -42
  47. package/dist/routers/HashRouter.d.ts +0 -9
  48. package/dist/routers/HashRouter.js +0 -41
  49. package/dist/routers/MemoryRouter.d.ts +0 -24
  50. package/dist/routers/MemoryRouter.js +0 -57
  51. package/dist/routers/Router.d.ts +0 -17
  52. package/dist/routers/Router.js +0 -59
  53. package/dist/routers/StaticRouter.d.ts +0 -6
  54. package/dist/routers/StaticRouter.js +0 -15
  55. package/dist/routers/createRouter.d.ts +0 -10
  56. package/dist/routers/createRouter.js +0 -41
package/README.md CHANGED
@@ -10,947 +10,775 @@
10
10
 
11
11
  </div>
12
12
 
13
- **Solid Router** brings fine-grained reactivity to route navigation, enabling your single-page application to become multi-paged without full page reloads. Fully integrated into the SolidJS ecosystem, Solid Router provides declarative syntax with features like universal rendering and parallel data fetching for best performance.
13
+ **Solid Router** brings fine-grained reactivity to route navigation. Routes are config objects — the single source of truth for matching *and* types — and the router upgrades HTML's own interaction verbs instead of wrapping them: `<a href={path}>` and `<form action={action}>` carry typed, URL-addressable values on real platform elements, intercepted by delegation, decorated with a shared attribute vocabulary, and fully functional without JavaScript.
14
14
 
15
15
  Explore the official [documentation](https://docs.solidjs.com/solid-router) for detailed guides and examples.
16
16
 
17
17
  ## Core Features
18
18
 
19
- - **All Routing Modes**:
20
- - [History-Based](https://docs.solidjs.com/solid-router/reference/components/router#router) for standard browser navigation
21
- - [Hash-Based](https://docs.solidjs.com/solid-router/reference/components/hash-router#hashrouter) for navigation based on URL hash
22
- - [Static Routing](https://docs.solidjs.com/solid-router/rendering-modes/ssr#server-side-rendering) for server-side rendering (_SSR_)
23
- - [Memory-Based](https://docs.solidjs.com/solid-router/reference/components/memory-router#memoryrouter) for testing in non-browser environments
24
- - **TypeScript**: Full integration for robust, type-safe development
25
- - **Universal Rendering**: Seamless rendering on both client and server environments
26
- - **Declarative**: Define routes as components or as an object
27
- - **Preload Functions**: Parallel data fetching, following the render-as-you-fetch pattern
28
- - **Dynamic Route Parameters**: Flexible URL patterns with parameters, optional segments, and wildcards
29
- - **Data APIs with Caching**: Reactive data fetching with deduplication and revalidation
19
+ - **Typed Routing**: URLs built through a typed path proxy inferred from your route config — `paths.users(2).settings` typechecks against the tree
20
+ - **Plain Anchors**: no link component — `<a>` elements get `aria-current`, `data-active`, and `data-pending` automatically via compiler-claimed anchors
21
+ - **Universal Rendering**: one factory for browser, hash, memory, and server rendering; history adapters are imports, so unused ones never enter your bundle
22
+ - **Preload Functions**: parallel data fetching following the render-as-you-fetch pattern, triggered eagerly on link hover/focus
23
+ - **Data APIs with Caching**: `query` and `action` with deduplication, revalidation, single-flight mutations, and progressive enhancement
24
+ - **Typed Search Params**: opt-in per-route [Standard Schema](https://github.com/standard-schema/standard-schema) validation — `search.page` is a `number`, not `"2"`
30
25
 
31
- ## Table of contents
26
+ ## Table of Contents
32
27
 
33
28
  - [Getting Started](#getting-started)
34
- - [Set Up the Router](#set-up-the-router)
35
- - [Configure Your Routes](#configure-your-routes)
36
- - [Create Links to Your Routes](#create-links-to-your-routes)
37
- - [Dynamic Routes](#dynamic-routes)
38
- - [Nested Routes](#nested-routes)
39
- - [Hash Mode Router](#hash-mode-router)
40
- - [Memory Mode Router](#memory-mode-router)
29
+ - [The Mental Model: Instance vs Hooks](#the-mental-model-instance-vs-hooks)
30
+ - [Route Definitions](#route-definitions)
31
+ - [Dynamic Routes](#dynamic-routes)
32
+ - [Typed Route Params](#typed-route-params)
33
+ - [Match Filters](#match-filters)
34
+ - [Optional Parameters](#optional-parameters)
35
+ - [Wildcard Routes](#wildcard-routes)
36
+ - [Multiple Paths](#multiple-paths)
37
+ - [Nested Routes](#nested-routes)
38
+ - [Lazy Route Subtrees](#lazy-route-subtrees)
39
+ - [File-System Routes](#file-system-routes)
40
+ - [Typed Paths](#typed-paths)
41
+ - [Links](#links)
42
+ - [Preload Functions](#preload-functions)
41
43
  - [Data APIs](#data-apis)
42
- - [Config Based Routing](#config-based-routing)
43
- - [Components](#components)
44
+ - [Typed Search Params](#typed-search-params)
45
+ - [Router Config Reference](#router-config-reference)
44
46
  - [Router Primitives](#router-primitives)
45
- - [useParams](#useparams)
46
- - [useNavigate](#usenavigate)
47
- - [useLocation](#uselocation)
48
- - [useSearchParams](#usesearchparams)
49
- - [useIsRouting](#useisrouting)
50
- - [useMatch](#usematch)
51
- - [useCurrentMatches](#useCurrentMatches)
52
- - [useBeforeLeave](#usebeforeleave)
47
+ - [Other Environments](#other-environments)
48
+ - [Server Integration](#server-integration)
49
+ - [Migration from 0.x](#migration-from-0x)
53
50
  - [SPAs in Deployed Environments](#spas-in-deployed-environments)
54
51
 
55
52
  ## Getting Started
56
53
 
57
- ### Set Up the Router
58
-
59
54
  ```bash
60
55
  # use preferred package manager
61
56
  npm add @solidjs/router
62
57
  ```
63
58
 
64
- Install `@solidjs/router`, then start your application by rendering the router component
59
+ Define your routes as config objects and create the router outside JSX. The instance is the provider component, and `paths` is a typed URL builder inferred from the tree:
60
+
61
+ ```tsx
62
+ // app/router.ts
63
+ import { lazy } from "solid-js";
64
+ import { createRouter } from "@solidjs/router";
65
65
 
66
- ```jsx
67
- import { render } from "solid-js/web";
68
- import { Router } from "@solidjs/router";
66
+ export const Router = createRouter({
67
+ routes: [
68
+ { path: "/", component: lazy(() => import("./pages/Home")) },
69
+ { path: "/about", component: lazy(() => import("./pages/About")) },
70
+ {
71
+ path: "/users/:id",
72
+ component: lazy(() => import("./pages/User")),
73
+ children: [
74
+ { path: "/", component: lazy(() => import("./pages/UserOverview")) },
75
+ { path: "/settings", component: lazy(() => import("./pages/UserSettings")) }
76
+ ]
77
+ },
78
+ { path: "*404", component: lazy(() => import("./pages/NotFound")) }
79
+ ]
80
+ });
69
81
 
70
- render(() => <Router />, document.getElementById("app"));
82
+ export const { paths } = Router;
71
83
  ```
72
84
 
73
- This sets up a Router that will match on the url to display the desired page
85
+ This one module serves everything: the client renders the instance, and on the server the same instance reads its location from the request (or the configured history when there is none — SSG, tests). There is no separate "server router" and no need to export the raw route array.
74
86
 
75
- ### Configure Your Routes
87
+ When a tree is composed across files (feature subtrees), wrap the extracted arrays in `defineRoutes` — an identity function that preserves the literal types inference feeds on (a bare extracted array silently widens to `string` paths without it or `as const`) and type-checks the definitions where they're written:
76
88
 
77
- Solid Router allows you to configure your routes using JSX:
89
+ ```tsx
90
+ // features/admin/routes.ts
91
+ export const adminRoutes = defineRoutes([
92
+ { path: "/admin", component: Admin, children: [/* ... */] }
93
+ ]);
94
+
95
+ // app/router.ts
96
+ export const Router = createRouter({ routes: [...appRoutes, ...adminRoutes] });
97
+ ```
78
98
 
79
- 1. Add each route to a `<Router>` using the `Route` component, specifying a path and a component to render when the user navigates to that path.
99
+ Its single-route sibling `defineRoute` also types `params` inside the route's own `component` and `preload` — see [Typed Route Params](#typed-route-params).
80
100
 
81
- ```jsx
82
- import { render } from "solid-js/web";
83
- import { Router, Route } from "@solidjs/router";
101
+ Mount it by rendering the instance. The render-prop child is your root layout — it always stays mounted, receives the matched content as `props.children`, and is the ideal place for top-level navigation and context providers:
84
102
 
85
- import Home from "./pages/Home";
86
- import Users from "./pages/Users";
103
+ ```tsx
104
+ // app/index.tsx
105
+ import { render } from "@solidjs/web";
106
+ import { Router } from "./router";
87
107
 
88
108
  render(
89
109
  () => (
90
110
  <Router>
91
- <Route path="/users" component={Users} />
92
- <Route path="/" component={Home} />
111
+ {props => (
112
+ <>
113
+ <h1>My Site with lots of pages</h1>
114
+ {props.children}
115
+ </>
116
+ )}
93
117
  </Router>
94
118
  ),
95
- document.getElementById("app")
119
+ document.getElementById("app")!
96
120
  );
97
121
  ```
98
122
 
99
- 2. Provide a root level layout
123
+ Links are plain anchors. Typed path nodes coerce to strings on the attribute, and the router intercepts clicks through delegation:
100
124
 
101
- This will always be there and won't update on page change. It is the ideal place to put top level navigation and Context Providers
125
+ ```tsx
126
+ import { paths } from "./router";
102
127
 
103
- ```jsx
104
- import { render } from "solid-js/web";
105
- import { Router, Route } from "@solidjs/router";
128
+ <nav>
129
+ <a href={paths()}>Home</a>
130
+ <a href={paths.about}>About</a>
131
+ <a href={paths.users(user.id).settings}>Settings</a>
132
+ </nav>;
133
+ ```
106
134
 
107
- import Home from "./pages/Home";
108
- import Users from "./pages/Users";
135
+ ## The Mental Model: Instance vs Hooks
109
136
 
110
- const App = (props) => (
111
- <>
112
- <h1>My Site with lots of pages</h1>
113
- {props.children}
114
- </>
115
- );
137
+ The API splits across two surfaces, and the line between them is precise: **could two concurrent server requests give different answers? If yes, it's a hook. If no, it's on the instance.**
116
138
 
117
- render(
118
- () => (
119
- <Router root={App}>
120
- <Route path="/users" component={Users} />
121
- <Route path="/" component={Home} />
122
- </Router>
123
- ),
124
- document.getElementById("app")
125
- );
126
- ```
139
+ The instance is shared — one module-level object serving every mount, every request, every test. It is deliberately non-stateful (on the server there are many "current locations" at once), so it carries only the app's static routing vocabulary. Hooks read the live session from context.
127
140
 
128
- 3. Create a catch-all route (404 page)
141
+ | Instance — facts about the *app* | Hooks — facts about the *session* |
142
+ | --- | --- |
143
+ | `paths` — how to spell URLs | `useLocation`, `useParams` — where am I |
144
+ | `match(url)` — how would a URL match | `useNavigate`, `usePreloadRoute` — move / warm |
145
+ | `routes`, `config` — what exists | `useIsRouting`, `useRouteMatches`, `useSearchParams` — live state |
129
146
 
130
- We can create catch-all routes for pages not found at any nested level of the router. We use `*` and optionally the name of a parameter to retrieve the rest of the path.
147
+ They compose as noun and verb — the instance supplies a typed URL, the hook acts on the current session:
131
148
 
132
- ```jsx
133
- import { render } from "solid-js/web";
134
- import { Router, Route } from "@solidjs/router";
149
+ ```tsx
150
+ const navigate = useNavigate();
151
+ navigate(paths.users(2)); // verb(noun)
135
152
 
136
- import Home from "./pages/Home";
137
- import Users from "./pages/Users";
138
- import NotFound from "./pages/404";
153
+ const params = useParams(paths.users); // hook, typed by the instance
154
+ ```
139
155
 
140
- const App = (props) => (
141
- <>
142
- <h1>My Site with lots of pages</h1>
143
- {props.children}
144
- </>
145
- );
156
+ **Hooks are the default; import the router only when you need typed URLs or matching outside a render.** Components that only read their session (params, location, string-path navigation) never need the instance — which also means component files don't form import cycles with the router module that references them in its config.
146
157
 
147
- render(
148
- () => (
149
- <Router root={App}>
150
- <Route path="/users" component={Users} />
151
- <Route path="/" component={Home} />
152
- <Route path="*404" component={NotFound} />
153
- </Router>
154
- ),
155
- document.getElementById("app")
156
- );
157
- ```
158
+ ## Route Definitions
158
159
 
159
- 4. Lazy-load route components
160
+ A route definition supports:
160
161
 
161
- This way, the `Users` and `Home` components will only be loaded if you're navigating to `/users` or `/`, respectively.
162
+ | key | type | description |
163
+ | -------------- | --------------------------------------- | ------------------------------------------------------------------ |
164
+ | `path` | `string \| string[]` | Path partial for this route segment |
165
+ | `component` | `Component` | Component rendered for the matched segment |
166
+ | `children` | `RouteDefinition \| RouteDefinition[] \| () => Promise<...>` | Nested route definitions, or a thunk for a [lazy subtree](#lazy-route-subtrees) |
167
+ | `preload` | `RoutePreloadFunc` | Called on preload intent (hover/focus) and navigation |
168
+ | `matchFilters` | `MatchFilters` | Additional constraints for matching parameters |
169
+ | `search` | `StandardSchemaV1` | Search-param validator; its types flow into `paths` and hooks |
170
+ | `info` | `Record<string, any>` | Arbitrary metadata, readable via `useRouteMatches` |
162
171
 
163
- ```jsx
164
- import { lazy } from "solid-js";
165
- import { render } from "solid-js/web";
166
- import { Router, Route } from "@solidjs/router";
172
+ The tree is **immutable and there is one router per app** — that's what makes `paths` and the typed hooks truthful, it lets matching compile once and be shared by every mount, request, and `match()` call, and it means delegation, link state, and preloading all have a single owner. Compose large apps by spreading subtrees into the config (see `defineRoutes` above); mounting a router inside another router is not supported (nested `<Routes>` has been gone since 0.10) and warns in development. Sections whose *code* shouldn't load up front are [lazy route subtrees](#lazy-route-subtrees) — still one tree, still typed.
167
173
 
168
- const Users = lazy(() => import("./pages/Users"));
169
- const Home = lazy(() => import("./pages/Home"));
174
+ ### Dynamic Routes
170
175
 
171
- const App = (props) => (
172
- <>
173
- <h1>My Site with lots of pages</h1>
174
- {props.children}
175
- </>
176
- );
176
+ Treat part of the path as a parameter with a colon:
177
177
 
178
- render(
179
- () => (
180
- <Router root={App}>
181
- <Route path="/users" component={Users} />
182
- <Route path="/" component={Home} />
183
- </Router>
184
- ),
185
- document.getElementById("app")
186
- );
178
+ ```tsx
179
+ const routes = defineRoutes([
180
+ { path: "/users", component: Users },
181
+ { path: "/users/:id", component: User }
182
+ ]);
187
183
  ```
188
184
 
189
- ### Create Links to Your Routes
190
-
191
- Use an anchor tag that takes you to a route:
185
+ As long as the URL fits the pattern, the `User` component shows, and `id` is available via `useParams`.
192
186
 
193
- ```jsx
194
- import { lazy } from "solid-js";
195
- import { render } from "solid-js/web";
196
- import { Router, Route } from "@solidjs/router";
197
-
198
- const Users = lazy(() => import("./pages/Users"));
199
- const Home = lazy(() => import("./pages/Home"));
200
-
201
- const App = (props) => (
202
- <>
203
- <nav>
204
- <a href="/about">About</a>
205
- <a href="/">Home</a>
206
- </nav>
207
- <h1>My Site with lots of pages</h1>
208
- {props.children}
209
- </>
210
- );
187
+ **Note on Animation/Transitions**: routes that share the same path match are treated as the same route. To force a re-render, wrap your component in a keyed `<Show>`:
211
188
 
212
- render(
213
- () => (
214
- <Router root={App}>
215
- <Route path="/users" component={Users} />
216
- <Route path="/" component={Home} />
217
- </Router>
218
- ),
219
- document.getElementById("app")
220
- );
189
+ ```tsx
190
+ <Show when={params.something} keyed>
191
+ <MyComponent />
192
+ </Show>
221
193
  ```
222
194
 
223
- ## Dynamic Routes
195
+ ### Typed Route Params
224
196
 
225
- If you don't know the path ahead of time, you might want to treat part of the path as a flexible parameter that is passed on to the component.
197
+ By default `params` is an open record — every key is `string | undefined`, even when the pattern guarantees it. Wrap a route in `defineRoute` and its `component` and `preload` are typed from the route's own `path`:
226
198
 
227
- ```jsx
228
- import { lazy } from "solid-js";
229
- import { render } from "solid-js/web";
230
- import { Router, Route } from "@solidjs/router";
231
-
232
- const Users = lazy(() => import("./pages/Users"));
233
- const User = lazy(() => import("./pages/User"));
234
- const Home = lazy(() => import("./pages/Home"));
199
+ ```tsx
200
+ import { defineRoute } from "@solidjs/router";
235
201
 
236
- render(
237
- () => (
238
- <Router>
239
- <Route path="/users" component={Users} />
240
- <Route path="/users/:id" component={User} />
241
- <Route path="/" component={Home} />
242
- </Router>
243
- ),
244
- document.getElementById("app")
245
- );
202
+ const story = defineRoute({
203
+ path: "/stories/:id/:tab?",
204
+ preload: ({ params }) => getStory(params.id), // params.id: string
205
+ component: props => (
206
+ <Story
207
+ id={props.params.id} // string — the pattern guarantees it
208
+ tab={props.params.tab} // string | undefined — optional param
209
+ />
210
+ )
211
+ });
246
212
  ```
247
213
 
248
- The colon indicates that `id` can be any string, and as long as the URL fits that pattern, the `User` component will show.
214
+ `defineRoute` is an identity function at runtime — the route object drops into `routes` (or a parent's `children`) like any plain object, and `path`, `children`, `matchFilters`, and `search` still flow into `paths` and the typed hooks. Params inherited from parent routes stay accessible as `string | undefined`; nested `children` type their own params only if they use `defineRoute` themselves.
249
215
 
250
- You can then access that `id` from within a route component with `useParams`.
216
+ For components declared away from their route, `RouteProps` takes a path witness — the same `paths` node you navigate with (`import type` keeps the instance out of the runtime graph, so no cycle) — plus an optional data type:
251
217
 
252
- **Note on Animation/Transitions**:
253
- Routes that share the same path match will be treated as the same route. If you want to force re-render you can wrap your component in a keyed `<Show>` like:
218
+ ```tsx
219
+ import type { RouteComponent, RouteProps } from "@solidjs/router";
220
+ import type { Router } from "./app/router";
254
221
 
255
- ```jsx
256
- <Show when={params.something} keyed>
257
- <MyComponent />
258
- </Show>
222
+ function Story(props: RouteProps<typeof Router.paths.stories, StoryData>) {
223
+ props.params.id; // string
224
+ }
225
+
226
+ // component-type form — props infer contextually
227
+ const Story: RouteComponent<typeof Router.paths.stories, StoryData> = props => (
228
+ <h1>{props.params.id}</h1>
229
+ );
230
+
231
+ // or, anywhere under the route:
232
+ const params = useParams(paths.stories); // typed from the tree
259
233
  ```
260
234
 
261
- ---
235
+ When no instance is in scope at the definition site — most notably [file-system route files](#file-system-routes), where the pattern lives in the filename — the witness can be the pattern string itself: `RouteProps<"/stories/:id">`.
262
236
 
263
- Each path parameter can be validated using a `MatchFilter`.
264
- This allows for more complex routing descriptions than just checking the presence of a parameter.
237
+ ### Match Filters
265
238
 
266
- ```jsx
267
- import { lazy } from "solid-js";
268
- import { render } from "solid-js/web";
269
- import { Router, Route } from "@solidjs/router";
270
- import type { MatchFilters } from "@solidjs/router";
239
+ Each parameter can be validated with a `MatchFilter` — an enum array, a regex, or a predicate. If validation fails, the route doesn't match:
271
240
 
272
- const User = lazy(() => import("./pages/User"));
241
+ ```tsx
242
+ import { int, type MatchFilters } from "@solidjs/router";
273
243
 
274
244
  const filters: MatchFilters = {
275
- parent: ["mom", "dad"], // allow enum values
276
- id: /^\d+$/, // only allow numbers
277
- withHtmlExtension: (v: string) => v.length > 5 && v.endsWith(".html"), // we want an `*.html` extension
245
+ parent: ["mom", "dad"], // enum values
246
+ id: /^\d+$/, // only numbers
247
+ withHtmlExtension: (v: string) => v.length > 5 && v.endsWith(".html")
278
248
  };
279
249
 
280
- render(
281
- () => (
282
- <Router>
283
- <Route
284
- path="/users/:parent/:id/:withHtmlExtension"
285
- component={User}
286
- matchFilters={filters}
287
- />
288
- </Router>
289
- ),
290
- document.getElementById("app")
291
- );
250
+ const routes = defineRoutes([
251
+ { path: "/users/:parent/:id/:withHtmlExtension", component: User, matchFilters: filters }
252
+ ]);
292
253
  ```
293
254
 
294
- Here, we have added the `matchFilters` prop. This allows us to validate the `parent`, `id` and `withHtmlExtension` parameters against the filters defined in `filters`.
295
- If the validation fails, the route will not match.
255
+ So `/users/mom/123/contact.html` matches, while `/users/aunt/123/contact.html` (invalid `parent`) and `/users/mom/me/contact.html` (non-numeric `id`) don't.
296
256
 
297
- So in this example:
257
+ The built-in `int` filter is *typed*: it constrains matching to integers at runtime and types the param as `number` at `paths` callsites:
298
258
 
299
- - `/users/mom/123/contact.html` would match,
300
- - `/users/dad/123/about.html` would match,
301
- - `/users/aunt/123/contact.html` would not match as `:parent` is not 'mom' or 'dad',
302
- - `/users/mom/me/contact.html` would not match as `:id` is not a number,
303
- - `/users/dad/123/contact` would not match as `:withHtmlExtension` is missing `.html`.
259
+ ```tsx
260
+ { path: "/users/:id", matchFilters: { id: int }, component: User }
304
261
 
305
- ---
262
+ paths.users(123); // ok
263
+ paths.users("abc"); // type error
264
+ ```
306
265
 
307
266
  ### Optional Parameters
308
267
 
309
- Parameters can be specified as optional by adding a question mark to the end of the parameter name:
268
+ Add a question mark to make a parameter optional:
310
269
 
311
- ```jsx
270
+ ```tsx
312
271
  // Matches stories and stories/123 but not stories/123/comments
313
- <Route path="/stories/:id?" component={Stories} />
272
+ { path: "/stories/:id?", component: Stories }
314
273
  ```
315
274
 
316
275
  ### Wildcard Routes
317
276
 
318
- `:param` lets you match an arbitrary name at that point in the path. You can use `*` to match any end of the path:
277
+ Use `*` to match any remainder of the path, optionally naming it to expose it as a parameter:
319
278
 
320
- ```jsx
321
- // Matches any path that begins with foo, including foo/, foo/a/, foo/a/b/c
322
- <Route path="foo/*" component={Foo} />
279
+ ```tsx
280
+ { path: "foo/*", component: Foo } // matches foo/, foo/a, foo/a/b/c
281
+ { path: "foo/*any", component: Foo } // rest of the path available as params.any
323
282
  ```
324
283
 
325
- If you want to expose the wild part of the path to the component as a parameter, you can name it:
326
-
327
- ```jsx
328
- <Route path="foo/*any" component={Foo} />
329
- ```
330
-
331
- Note that the wildcard token must be the last part of the path; `foo/*any/bar` won't create any routes.
284
+ The wildcard token must be the last part of the path; `foo/*any/bar` won't create any routes.
332
285
 
333
286
  ### Multiple Paths
334
287
 
335
- Routes also support defining multiple paths using an array. This allows a route to remain mounted and not rerender when switching between two or more locations that it matches:
336
-
337
- ```jsx
338
- // Navigating from login to register does not cause the Login component to re-render
339
- <Route path={["login", "register"]} component={Login} />
340
- ```
341
-
342
- ## Nested Routes
343
-
344
- The following two route definitions have the same result:
345
-
346
- ```jsx
347
- <Route path="/users/:id" component={User} />
348
- ```
288
+ An array of paths lets a route stay mounted (no re-render) when switching between locations it matches:
349
289
 
350
- ```jsx
351
- <Route path="/users">
352
- <Route path="/:id" component={User} />
353
- </Route>
290
+ ```tsx
291
+ // Navigating from login to register does not re-render Login
292
+ { path: ["login", "register"], component: Login }
354
293
  ```
355
294
 
356
- `/users/:id` renders the `<User/>` component, and `/users/` is an empty route.
295
+ ### Nested Routes
357
296
 
358
- Only leaf Route nodes (innermost `Route` components) are given a route. If you want to make the parent its own route, you have to specify it separately:
297
+ Only leaf nodes become routes. A parent with a `component` wraps its children, which render where the parent places `props.children`:
359
298
 
360
- ```jsx
361
- //This won't work the way you'd expect
362
- <Route path="/users" component={Users}>
363
- <Route path="/:id" component={User} />
364
- </Route>
365
-
366
- // This works
367
- <Route path="/users" component={Users} />
368
- <Route path="/users/:id" component={User} />
369
-
370
- // This also works
371
- <Route path="/users">
372
- <Route path="/" component={Users} />
373
- <Route path="/:id" component={User} />
374
- </Route>
375
- ```
376
-
377
- You can also take advantage of nesting by using `props.children` passed to the route component.
378
-
379
- ```jsx
299
+ ```tsx
380
300
  function PageWrapper(props) {
381
301
  return (
382
302
  <div>
383
- <h1> We love our users! </h1>
303
+ <h1>We love our users!</h1>
384
304
  {props.children}
385
- <A href="/">Back Home</A>
305
+ <a href={paths()}>Back Home</a>
386
306
  </div>
387
307
  );
388
308
  }
389
309
 
390
- <Route path="/users" component={PageWrapper}>
391
- <Route path="/" component={Users} />
392
- <Route path="/:id" component={User} />
393
- </Route>;
394
- ```
395
-
396
- The routes are still configured the same, but now the route elements will appear inside the parent element where the `props.children` was declared.
397
-
398
- You can nest indefinitely - just remember that only leaf nodes will become their own routes. In this example, the only route created is `/layer1/layer2`, and it appears as three nested divs.
399
-
400
- ```jsx
401
- <Route
402
- path="/"
403
- component={(props) => <div>Onion starts here {props.children}</div>}
404
- >
405
- <Route
406
- path="layer1"
407
- component={(props) => <div>Another layer {props.children}</div>}
408
- >
409
- <Route path="layer2" component={() => <div>Innermost layer</div>} />
410
- </Route>
411
- </Route>
310
+ const routes = defineRoutes([
311
+ {
312
+ path: "/users",
313
+ component: PageWrapper,
314
+ children: [
315
+ { path: "/", component: Users },
316
+ { path: "/:id", component: User }
317
+ ]
318
+ }
319
+ ]);
412
320
  ```
413
321
 
414
- ## Preload Functions
415
-
416
- Even with smart caches it is possible that we have waterfalls both with view logic and with lazy loaded code. With preload functions, we can instead start fetching the data parallel to loading the route, so we can use the data as soon as possible. The preload function is called when the Route is loaded or eagerly when links are hovered.
417
-
418
- As its only argument, the preload function is passed an object that you can use to access route information:
419
-
420
- ```js
421
- import { lazy } from "solid-js";
422
- import { Route } from "@solidjs/router";
423
-
424
- const User = lazy(() => import("./pages/users/[id].js"));
322
+ You can nest indefinitely. In this example the only route created is `/layer1/layer2`, rendered as three nested divs:
425
323
 
426
- // preload function
427
- function preloadUser({ params, location }) {
428
- // do preloading
324
+ ```tsx
325
+ {
326
+ path: "/",
327
+ component: props => <div>Onion starts here {props.children}</div>,
328
+ children: [{
329
+ path: "layer1",
330
+ component: props => <div>Another layer {props.children}</div>,
331
+ children: [{ path: "layer2", component: () => <div>Innermost layer</div> }]
332
+ }]
429
333
  }
430
-
431
- // Pass it in the route definition
432
- <Route path="/users/:id" component={User} preload={preloadUser} />;
433
334
  ```
434
335
 
435
- | key | type | description |
436
- | -------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
437
- | params | object | The route parameters (same value as calling `useParams()` inside the route component) |
438
- | location | `{ pathname, search, hash, query, state, key}` | An object that you can use to get more information about the path (corresponds to [`useLocation()`](#uselocation)) |
439
- | intent | `"initial", "navigate", "native", "preload"` | Indicates why this function is being called. <ul><li>"initial" - the route is being initially shown (ie page load)</li><li>"native" - navigate originated from the browser (eg back/forward)</li><li>"navigate" - navigate originated from the router (eg call to navigate or anchor clicked)</li><li>"preload" - not navigating, just preloading (eg link hover)</li></ul> |
336
+ ### Lazy Route Subtrees
440
337
 
441
- A common pattern is to export the preload function and data wrappers that corresponds to a route in a dedicated `route.data.js` file. This way, the data function can be imported without loading anything else.
338
+ `children` also accepts a thunk, so a whole section's route table (not just its components) stays out of the initial bundle:
442
339
 
443
- ```js
444
- import { lazy } from "solid-js";
445
- import { Route } from "@solidjs/router";
446
- import preloadUser from "./pages/users/[id].data.js";
447
- const User = lazy(() => import("/pages/users/[id].js"));
340
+ ```tsx
341
+ // admin/routes.ts
342
+ export default defineRoutes([
343
+ { path: "/", component: lazy(() => import("./Dashboard")) },
344
+ { path: "/users/:id", matchFilters: { id: int }, component: lazy(() => import("./User")) }
345
+ ]);
448
346
 
449
- // In the Route definition
450
- <Route path="/users/:id" component={User} preload={preloadUser} />;
347
+ // app.ts
348
+ const router = createRouter({
349
+ routes: [
350
+ { path: "/", component: Home },
351
+ { path: "/admin", component: AdminShell, children: () => import("./admin/routes") }
352
+ ]
353
+ });
451
354
  ```
452
355
 
453
- The `preload` function's return value is passed to the page component for any intent other than `"preload"`, allowing you to initialize data or alternatively use our new Data APIs:
356
+ The import only fires when something needs the subtree — hovering a link into it, navigating into it, or the server matching a URL beneath it. Until then the tree carries a placeholder that knows every URL under `/admin` belongs to the subtree without knowing its contents (static sibling routes still win without triggering the load). Everything folds in as if the routes were inline:
454
357
 
455
- ## Data APIs
358
+ - **Types**: TypeScript never runs the thunk — inference flows through the import's promise type, so `paths.admin.users(2)` typechecks (match filters and search schemas included) before any of the subtree's code exists client-side. The module's `default` or `routes` export is used. Only tables genuinely built at runtime (typed as plain `RouteDefinition[]`) degrade to untyped.
359
+ - **Navigation**: the table load folds into the navigation transition — the old screen holds until the subtree (and its matched components) are ready, exactly like a `lazy()` route component.
360
+ - **Preloading**: hover intent kicks the table load, and when it lands the preload continues into the inner routes' components and `preload` functions — one cascading warm-up from the earliest possible moment.
361
+ - **Server**: SSR resolves matched boundaries during the render (use the streaming entry points — `renderToStream`/`renderToStringAsync` — as with any async work), and the single-flight collector resolves them before its data pass.
456
362
 
457
- Keep in mind that these are entirely optional, but they demonstrate the power of our preload mechanism.
363
+ Resolution is cached per thunk and append-only: the tree never changes shape after a subtree lands, it just gets more specific. Keep thunks deterministic — `() => import(...)` — rather than switching tables on runtime state.
458
364
 
459
- ### `query`
460
-
461
- To prevent duplicate fetching and to handle refetching triggers, we provide a query API that accepts a function and returns the same function.
462
-
463
- ```jsx
464
- const getUser = query(async (id) => {
465
- return (await fetch(`/api/users/${id}`)).json();
466
- }, "users"); // used as the query key + serialized arguments
467
- ```
365
+ ### File-System Routes
468
366
 
469
- It is expected that the arguments to the query function are serializable.
367
+ The `@solidjs/router/fs` adapter turns a `file-routes` manifest into route definitions — the app imports the virtual module, the adapter maps it:
470
368
 
471
- This query accomplishes the following:
369
+ ```tsx
370
+ import { pageRoutes } from "virtual:file-routes";
371
+ import { fileRoutes } from "@solidjs/router/fs";
472
372
 
473
- 1. It does deduping on the server for the lifetime of the request.
474
- 2. It fills a preload cache in the browser which lasts 5 seconds. When a route is preloaded on hover or when preload is called when entering a route it will make sure to dedupe calls.
475
- 3. We have a reactive refetch mechanism based on key. So we can tell routes that aren't new to retrigger on action revalidation.
476
- 4. It will serve as a back/forward cache for browser navigation up to 5 mins. Any user based navigation or link click bypasses this cache. Revalidation or new fetch updates the cache.
373
+ export const Router = createRouter({ routes: fileRoutes(pageRoutes) });
374
+ ```
477
375
 
478
- Using it with preload function might look like:
376
+ Route files export their component as `default` and everything else as a `route` config export, which is spread into the definition. Inside a route file the pattern lives in the filename, so there's no `paths` node to witness with at the definition site — `defineFileRoute` takes the pattern string instead, types `preload`'s params from it, and validates `matchFilters` along the way. The config then doubles as the component's [`RouteProps`](#typed-route-params) witness, typing `params` from the pattern and `data` from the `preload`'s return type:
479
377
 
480
- ```js
481
- import { lazy } from "solid-js";
482
- import { Route } from "@solidjs/router";
483
- import { getUser } from ... // the query function
378
+ ```tsx
379
+ // routes/blog/[id].tsx
380
+ import { int } from "@solidjs/router";
381
+ import { defineFileRoute } from "@solidjs/router/fs";
484
382
 
485
- const User = lazy(() => import("./pages/users/[id].js"));
383
+ export const route = defineFileRoute("/blog/:id", {
384
+ matchFilters: { id: int },
385
+ preload: ({ params }) => getPost(params.id) // params.id: string
386
+ });
486
387
 
487
- // preload function
488
- function preloadUser({params, location}) {
489
- void getUser(params.id)
388
+ export default function Post(props: RouteProps<typeof route>) {
389
+ props.params.id; // string
390
+ props.data; // ReturnType of the preload above
490
391
  }
491
-
492
- // Pass it in the route definition
493
- <Route path="/users/:id" component={User} preload={preloadUser} />;
494
392
  ```
495
393
 
496
- Inside your page component you:
394
+ The pattern string is a typing witness — at runtime the manifest's path (from the filename) is the source of truth. With the plugin's `types` option generating a literal declaration for the virtual module, the file paths flow into `paths` and the typed hooks like a hand-written tree — `paths.blog(42)` typechecks, filters and search schemas included, and `useParams(paths.blog)` works as usual anywhere under the route.
497
395
 
498
- ```jsx
499
- // pages/users/[id].js
500
- import { getUser } from ... // the query function
396
+ ## Typed Paths
501
397
 
502
- export default function User(props) {
503
- const user = createAsync(() => getUser(props.params.id));
504
- return <h1>{user().name}</h1>;
505
- }
506
- ```
398
+ `paths` is a proxy inferred from the route tree. Property access descends into static segments, calls bind params, and it mirrors URL anatomy — params, then a search object, then a hash string:
507
399
 
508
- Cached function has a few useful methods for getting the key that are useful for invalidation.
509
-
510
- ```ts
511
- let id = 5;
512
-
513
- getUser.key; // returns "users"
514
- getUser.keyFor(id); // returns "users[5]"
400
+ ```tsx
401
+ paths.users(123) // ok — matchFilters flow into the callsite
402
+ paths.users(2).settings // chainable into children
403
+ paths.users(2, { tab: "x" }, "comments") // "/users/2?tab=x#comments"
404
+ paths.about() // zero-arg/search calls terminate to a plain string
405
+ paths() // "/" — the root
515
406
  ```
516
407
 
517
- You can revalidate the query using the `revalidate` method or you can set `revalidate` keys on your response from your actions. If you pass the whole key it will invalidate all the entries for the query (ie "users" in the example above). You can also invalidate a single entry by using `keyFor`.
408
+ Every node coerces via `toString`, so nodes drop straight into `href`, `navigate()`, and `redirect()` without explicit termination. Accessing a segment that doesn't exist in the tree, or binding a param with the wrong type, is a compile error.
409
+
410
+ ## Links
518
411
 
519
- `query` can be defined anywhere and then used inside your components with:
412
+ There is no link component. Use `<a>`; the router intercepts same-origin clicks through delegation and manages link state through compiler-claimed anchors — correct at creation (so late mounts under `<Show>`, `<For>`, or portals are never stale) and refreshed if a dynamic `href` changes.
520
413
 
521
- ### `createAsync`
414
+ Behavior modifiers are attributes, so they work identically in client, server-rendered, and third-party markup:
522
415
 
523
- This is light wrapper over `createResource` that aims to serve as stand-in for a future primitive we intend to bring to Solid core in 2.0. It is a simpler async primitive where the function tracks like `createMemo` and it expects a promise back that it turns into a Signal. Reading it before it is ready causes Suspense/Transitions to trigger.
416
+ | attribute | description |
417
+ | ---------- | ------------------------------------------------------------------------------ |
418
+ | `replace` | Replace the history entry instead of pushing |
419
+ | `noscroll` | Turn off scrolling to the top after navigation |
420
+ | `state` | JSON string [pushed](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) onto the history stack |
421
+ | `preload` | Set to `"false"` to opt this link out of hover/focus preloading |
422
+ | `link` | Marks a router link when `explicitLinks` is enabled |
423
+ | `target` | Any value (e.g. `_self`) opts the anchor out of router handling |
524
424
 
525
- ```jsx
526
- const user = createAsync((currentValue) => getUser(params.id));
425
+ ```tsx
426
+ <a href={paths.login} replace>Log in</a>
427
+ <a href={paths.docs} noscroll>Docs</a>
428
+ <a href="https://example.com">External — untouched</a>
527
429
  ```
528
430
 
529
- It also preserves `latest` field from `createResource`. Note that it will be removed in the future.
431
+ Active and pending state is styled with CSS — one vocabulary for every kind of link:
530
432
 
531
- ```jsx
532
- const user = createAsync((currentValue) => getUser(params.id));
533
- return <h1>{user.latest.name}</h1>;
433
+ ```css
434
+ nav a[aria-current="page"] { font-weight: 600; } /* exact match */
435
+ nav a[data-active] { color: var(--accent); } /* exact or prefix match */
436
+ a[data-pending] { opacity: 0.6; } /* target of in-flight navigation */
534
437
  ```
535
438
 
536
- Using `query` in `createResource` directly won't work properly as the fetcher is not reactive and it won't invalidate properly.
439
+ (The root path only ever matches exactly, so `href={paths()}` doesn't light up on every page.)
537
440
 
538
- ### `createAsyncStore`
441
+ For component-library links that need reactive state beyond CSS, `useLinkState` is the programmatic counterpart of the attribute vocabulary:
539
442
 
540
- Similar to `createAsync` except it uses a deeply reactive store. Perfect for applying fine-grained changes to large model data that updates.
541
- It also supports `latest` field which will be removed in the future.
443
+ ```tsx
444
+ import { useLinkState } from "@solidjs/router";
542
445
 
543
- ```jsx
544
- const todos = createAsyncStore(() => getTodos());
446
+ function TabLink(props: { href: string; children: JSX.Element }) {
447
+ const link = useLinkState(() => props.href);
448
+ return (
449
+ <a href={props.href} class="tab" data-selected={link.active() || undefined}>
450
+ {props.children}
451
+ </a>
452
+ );
453
+ }
545
454
  ```
546
455
 
547
- ### `action`
456
+ ## Preload Functions
548
457
 
549
- Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response helpers can be found below.
458
+ Even with smart caches, waterfalls happen when data fetching waits on view logic or lazy-loaded code. Preload functions start fetching data in parallel with loading the route — called when a route renders, and eagerly when links are hovered or focused.
550
459
 
551
- ```jsx
552
- import { action, revalidate, redirect } from "@solidjs/router"
460
+ ```tsx
461
+ import { lazy } from "solid-js";
553
462
 
554
- // anywhere
555
- const myAction = action(async (data) => {
556
- await doMutation(data);
557
- throw redirect("/", { revalidate: getUser.keyFor(data.id) }); // throw a response to do a redirect
558
- });
463
+ const User = lazy(() => import("./pages/users/[id].js"));
559
464
 
560
- // in component
561
- <form action={myAction} method="post" />
465
+ function preloadUser({ params, location }) {
466
+ void getUser(params.id);
467
+ }
562
468
 
563
- //or
564
- <button type="submit" formaction={myAction}></button>
469
+ const routes = defineRoutes([{ path: "/users/:id", component: User, preload: preloadUser }]);
565
470
  ```
566
471
 
567
- Actions only work with post requests, so make sure to put `method="post"` on your form.
472
+ The preload function receives:
568
473
 
569
- Sometimes it might be easier to deal with typed data instead of `FormData` and adding additional hidden fields. For that reason Actions have a with method. That works similar to `bind` which applies the arguments in order.
474
+ | key | type | description |
475
+ | -------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
476
+ | params | object | The route parameters (same value as `useParams()` inside the route component) |
477
+ | location | `{ pathname, search, hash, query, state, key }` | Path information (corresponds to [`useLocation()`](#uselocation)) |
478
+ | intent | `"initial" \| "navigate" \| "native" \| "preload"` | Why this is being called: `initial` — first render; `navigate` — router navigation; `native` — browser back/forward; `preload` — link hover/focus, not navigating |
570
479
 
571
- Picture an action that deletes Todo Item:
480
+ The factory-level `preload` option is the app-wide counterpart: it runs once per mount/request with the merged params of every match, and its result reaches the root render-prop as `props.data`.
572
481
 
573
- ```js
574
- const deleteTodo = action(async (formData: FormData) => {
575
- const id = Number(formData.get("id"))
576
- await api.deleteTodo(id)
577
- })
482
+ ## Data APIs
578
483
 
579
- <form action={deleteTodo} method="post">
580
- <input type="hidden" name="id" value={todo.id} />
581
- <button type="submit">Delete</button>
582
- </form>
583
- ```
484
+ These are entirely optional, but they demonstrate the power of the preload mechanism.
584
485
 
585
- Instead with `with` you can write this:
486
+ ### `query`
586
487
 
587
- ```js
588
- const deleteTodo = action(api.deleteTodo)
488
+ Wrap a fetching function to dedupe calls and participate in revalidation:
589
489
 
590
- <form action={deleteTodo.with(todo.id)} method="post">
591
- <button type="submit">Delete</button>
592
- </form>
490
+ ```tsx
491
+ const getUser = query(async id => {
492
+ return (await fetch(`/api/users/${id}`)).json();
493
+ }, "users"); // query key; arguments are serialized alongside it
593
494
  ```
594
495
 
595
- Actions also take a second argument which can be the name or an option object with `name` and `onComplete`. `name` is used to identify SSR actions that aren't server functions (see note below). `onComplete` allows you to configure behavior when `action`s complete. Keep in mind `onComplete` does not work when JavaScript is disabled.
596
-
597
- #### Notes on `<form>` implementation and SSR
496
+ A query:
598
497
 
599
- This requires stable references as you can only serialize a string as an attribute, and across SSR they'd need to match. The solution is providing a unique name.
600
-
601
- ```jsx
602
- const myAction = action(async (args) => {}, "my-action");
603
- ```
498
+ 1. Dedupes on the server for the lifetime of the request.
499
+ 2. Fills a preload cache in the browser lasting 5 seconds, so hover preloads and route entry share one fetch.
500
+ 3. Refetches reactively by key on action revalidation.
501
+ 4. Serves as a back/forward cache for browser navigation up to 5 minutes; user-initiated navigation bypasses it.
604
502
 
605
- ### `useAction`
503
+ Consume results directly with Solid primitives — there is no router-specific async wrapper:
606
504
 
607
- Instead of forms you can use actions directly by wrapping them in a `useAction` primitive. This is how we get the router context.
505
+ ```tsx
506
+ const user = createMemo(() => getUser(params.id));
507
+ return <h1>{user().name}</h1>;
608
508
 
609
- ```jsx
610
- // in component
611
- const submit = useAction(myAction);
612
- submit(...args);
509
+ // deeply reactive object data
510
+ const todos = createProjection(() => getTodos(), []);
613
511
  ```
614
512
 
615
- The outside of a form context you can use custom data instead of formData, and these helpers preserve types. However, even when used with server functions (in projects like SolidStart) this requires client side javascript and is not Progressive Enhanceable like forms are.
616
-
617
- ### `useSubmission`/`useSubmissions`
513
+ Keys support targeted invalidation:
618
514
 
619
- Are used to injecting the optimistic updates while actions are in flight. They either return a single Submission(latest) or all that match with an optional filter function.
620
-
621
- ```jsx
622
- type Submission<T, U> = {
623
- readonly input: T;
624
- readonly result?: U;
625
- readonly pending: boolean;
626
- readonly url: string;
627
- clear: () => void;
628
- retry: () => void;
629
- };
630
-
631
- const submissions = useSubmissions(action, (input) => filter(input));
632
- const submission = useSubmission(action, (input) => filter(input));
515
+ ```ts
516
+ getUser.key; // "users"
517
+ getUser.keyFor(5); // "users[5]"
633
518
  ```
634
519
 
635
- ### Response Helpers
520
+ Revalidate with the `revalidate` export or by setting `revalidate` keys on action responses — the whole key invalidates every entry for the query, `keyFor` invalidates one.
636
521
 
637
- These are used to communicate router navigations from query/actions, and can include invalidation hints. Generally these are thrown to not interfere the with the types and make it clear that function ends execution at that point.
522
+ ### `action`
638
523
 
639
- #### `redirect(path, options)`
524
+ A router action is *an action with a URL* — Solid's mutation primitive plus URL addressability, submission tracking, and response handling. Data helpers come from the router; response helpers (`redirect`, `reload`) come from `@solidjs/web` — they're protocol-level and work without the router:
640
525
 
641
- Redirects to the next route
526
+ ```tsx
527
+ import { action } from "@solidjs/router";
528
+ import { redirect } from "@solidjs/web";
529
+ import { paths } from "./router";
642
530
 
643
- ```js
644
- const getUser = query(() => {
645
- const user = await api.getCurrentUser()
646
- if (!user) throw redirect("/login");
647
- return user;
648
- })
531
+ const updateUser = action(async (form: FormData) => {
532
+ await db.users.update(form.get("id"), form);
533
+ throw redirect(paths.users(form.get("id"))); // typed paths work in redirects
534
+ });
649
535
  ```
650
536
 
651
- #### `reload(options)`
652
-
653
- Reloads the data on the current page
654
-
655
- ```js
656
- const getTodo = query(async (id: number) => {
657
- const todo = await fetchTodo(id);
658
- return todo;
659
- }, "todo");
537
+ ```tsx
538
+ <form action={updateUser} method="post">
539
+ <button>Save</button>
540
+ </form>
660
541
 
661
- const updateTodo = action(async (todo: Todo) => {
662
- await updateTodo(todo.id, todo);
663
- reload({ revalidate: getTodo.keyFor(todo.id) });
664
- });
542
+ // or
543
+ <button type="submit" formaction={updateUser}>Save</button>
665
544
  ```
666
545
 
667
- ## Config Based Routing
546
+ Actions only work with POST requests, so put `method="post"` on your form. Submitting forms get `aria-busy="true"` automatically while the action (including its revalidation) is in flight — the same CSS story as links:
668
547
 
669
- You don't have to use JSX to set up your routes; you can pass an array of route definitions:
548
+ ```css
549
+ form[aria-busy] button { pointer-events: none; opacity: 0.6; }
550
+ ```
670
551
 
671
- ```jsx
672
- import { lazy } from "solid-js";
673
- import { render } from "solid-js/web";
674
- import { Router } from "@solidjs/router";
552
+ Forms work without JavaScript: a real POST, a redirect back, and the result seeded into submission state through a one-shot flash cookie. Single-flight mutations are on by default — the mutation response carries the refreshed route data in the same round trip.
675
553
 
676
- const routes = [
677
- {
678
- path: "/users",
679
- component: lazy(() => import("/pages/users.js")),
680
- },
681
- {
682
- path: "/users/:id",
683
- component: lazy(() => import("/pages/users/[id].js")),
684
- children: [
685
- {
686
- path: "/",
687
- component: lazy(() => import("/pages/users/[id]/index.js")),
688
- },
689
- {
690
- path: "/settings",
691
- component: lazy(() => import("/pages/users/[id]/settings.js")),
692
- },
693
- {
694
- path: "/*all",
695
- component: lazy(() => import("/pages/users/[id]/[...all].js")),
696
- },
697
- ],
698
- },
699
- {
700
- path: "/",
701
- component: lazy(() => import("/pages/index.js")),
702
- },
703
- {
704
- path: "/*all",
705
- component: lazy(() => import("/pages/[...all].js")),
706
- },
707
- ];
554
+ Delegation doesn't require the action's module on the client either. A form bound directly to a server action in a server-only module (a server component) renders a plain `action="/_server?id=...&args=..."` — a self-describing URL. On submit, the router synthesizes the invocation from it: the form data posts to that URL through the server-function transport, `.with()` arguments ride along in the query string, and submissions, `aria-busy`, redirects, revalidation, and single-flight data flow through the normal pipeline. The handler loads lazily on first such submit, so router-only bundles don't carry the data layer. The no-JS POST above remains the fallback only for clients that actually have no JavaScript. (Client-only actions — `action(fn, "name")` without `use server` — are their module's JS by definition and still require it on the client.)
708
555
 
709
- render(() => <Router>{routes}</Router>, document.getElementById("app"));
710
- ```
556
+ For optimistic UI, attach owner-scoped hooks to the action and use Solid's optimistic primitives for rendered state:
711
557
 
712
- Also you can pass a single route definition object for a single route:
558
+ ```tsx
559
+ import { createOptimisticStore } from "solid-js";
560
+ import { action, query } from "@solidjs/router";
713
561
 
714
- ```jsx
715
- import { lazy } from "solid-js";
716
- import { render } from "solid-js/web";
717
- import { Router } from "@solidjs/router";
718
-
719
- const route = {
720
- path: "/",
721
- component: lazy(() => import("/pages/index.js")),
722
- };
562
+ const getTodos = query(async () => fetchTodos(), "todos");
563
+ const [todos, setTodos] = createOptimisticStore(() => getTodos(), []);
723
564
 
724
- render(() => <Router>{route}</Router>, document.getElementById("app"));
565
+ const addTodo = action(async todo => {
566
+ await saveTodo(todo);
567
+ return { ok: true, todo };
568
+ }, "add-todo").onSubmit(todo => {
569
+ setTodos(items => {
570
+ items.push({ ...todo, pending: true });
571
+ });
572
+ });
725
573
  ```
726
574
 
727
- ## Alternative Routers
575
+ `onSubmit(...)` registers a listener in the current reactive owner — multiple components can register against the same action, and hooks are removed when their owner is disposed. `onSettled(...)` works the same way for observing completed submissions.
728
576
 
729
- ### Hash Mode Router
577
+ The preferred pattern is returning values and letting the client interpret the result; thrown errors are still captured on `Submission.error` as an escape hatch.
730
578
 
731
- By default, Solid Router uses `location.pathname` as route path. You can simply switch to hash mode through using `<HashRouter>`.
579
+ Actions have a `with` method (like `bind`) for typed arguments instead of hidden form fields:
732
580
 
733
- ```jsx
734
- import { HashRouter } from "@solidjs/router";
581
+ ```tsx
582
+ const deleteTodo = action(api.deleteTodo);
735
583
 
736
- <HashRouter />;
584
+ <form action={deleteTodo.with(todo.id)} method="post">
585
+ <button type="submit">Delete</button>
586
+ </form>;
737
587
  ```
738
588
 
739
- ### Memory Mode Router
589
+ Since form actions serialize to string attributes that must match across SSR, actions that aren't server functions need a stable name: `action(fn, "my-action")`.
740
590
 
741
- You can also use memory mode router for testing purpose.
591
+ ### `useAction`
742
592
 
743
- ```jsx
744
- import { MemoryRouter } from "@solidjs/router";
593
+ Call an action directly instead of through a form — this is how the router context is captured. Outside a form you can pass typed data instead of `FormData`, but this requires client-side JavaScript and is not progressively enhanceable:
745
594
 
746
- <MemoryRouter />;
595
+ ```tsx
596
+ const submit = useAction(myAction);
597
+ submit(...args);
747
598
  ```
748
599
 
749
- ### SSR Routing
750
-
751
- For SSR you can use the static router directly or the browser Router defaults to it on the server, just pass in the url.
600
+ ### `useSubmissions`
752
601
 
753
- ```jsx
754
- import { isServer } from "solid-js/web";
755
- import { Router } from "@solidjs/router";
602
+ Returns settled submission records for an action — the durable history layer, not in-flight state. Useful for reading completed results, clearing old submissions, retrying, or replaying settled errors:
756
603
 
757
- <Router url={isServer ? req.url : ""} />;
604
+ ```tsx
605
+ const submissions = useSubmissions(action, input => filter(input));
606
+ const latest = submissions.at(-1);
607
+ // { input, result?, error, url, clear(), retry() }
758
608
  ```
759
609
 
760
- ## Components
610
+ Use Solid's `createOptimistic` / `createOptimisticStore` for in-flight UI.
761
611
 
762
- ### `<Router>`
612
+ ## Typed Search Params
763
613
 
764
- This is the main Router component for the browser.
614
+ Give a route a [Standard Schema](https://github.com/standard-schema/standard-schema) validator (Valibot, Zod, ArkType, hand-rolled…) and its types flow into `paths` and `useSearchParams`:
765
615
 
766
- | prop | type | description |
767
- | ------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
768
- | children | `JSX.Element`, `RouteDefinition`, or `RouteDefinition[]` | The route definitions |
769
- | root | Component | Top level layout component |
770
- | base | string | Base url to use for matching routes |
771
- | actionBase | string | Root url for server actions, default: `/_server` |
772
- | preload | boolean | Enables/disables preloads globally, default: `true` |
773
- | explicitLinks | boolean | Disables all anchors being intercepted and instead requires `<A>`. Default: `false`. (To disable interception for a specific link, set `target` to any value, e.g. `<a target="_self">`.) |
616
+ ```tsx
617
+ import * as v from "valibot";
774
618
 
775
- ### `<A>`
776
-
777
- Like the `<a>` tag but supports automatic apply of base path + relative paths and active class styling (requires client side JavaScript).
778
-
779
- The `<A>` tag has an `active` class if its href matches the current location, and `inactive` otherwise. **Note:** By default matching includes locations that are descendants (eg. href `/users` matches locations `/users` and `/users/123`), use the boolean `end` prop to prevent matching these. This is particularly useful for links to the root route `/` which would match everything.
619
+ const routes = defineRoutes([
620
+ {
621
+ path: "/search",
622
+ component: Search,
623
+ search: v.object({
624
+ q: v.optional(v.string(), ""),
625
+ page: v.optional(v.pipe(v.unknown(), v.transform(Number)), 1)
626
+ })
627
+ }
628
+ ]);
629
+ ```
780
630
 
781
- | prop | type | description |
782
- | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
783
- | href | string | The path of the route to navigate to. This will be resolved relative to the route that the link is in, but you can preface it with `/` to refer back to the root. |
784
- | noScroll | boolean | If true, turn off the default behavior of scrolling to the top of the new page |
785
- | replace | boolean | If true, don't add a new entry to the browser history. (By default, the new page will be added to the browser history, so pressing the back button will take you to the previous route.) |
786
- | state | unknown | [Push this value](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) to the history stack when navigating |
787
- | inactiveClass | string | The class to show when the link is inactive (when the current location doesn't match the link) |
788
- | activeClass | string | The class to show when the link is active |
789
- | end | boolean | If `true`, only considers the link to be active when the current location matches the `href` exactly; if `false`, check if the current location _starts with_ `href` |
631
+ ```tsx
632
+ const [search, setSearch] = useSearchParams(paths.search);
633
+ search.page; // number (parsed, not "2")
634
+ setSearch({ page: search.page + 1 }); // typed setter
790
635
 
791
- ### `<Navigate />`
636
+ <a href={paths.search({ q: "solid", page: 2 })}>Search</a>; // typed builder
637
+ ```
792
638
 
793
- Solid Router provides a `Navigate` component that works similarly to `A`, but it will _immediately_ navigate to the provided path as soon as the component is rendered. It also uses the `href` prop, but you have the additional option of passing a function to `href` that returns a path to navigate to:
639
+ Without a schema, `useSearchParams()` behaves as before: raw string values, merge-on-set semantics (`''`, `undefined`, and `null` remove keys), navigation-like updates with auto-scrolling disabled.
794
640
 
795
- ```jsx
796
- function getPath({ navigate, location }) {
797
- // navigate is the result of calling useNavigate(); location is the result of calling useLocation().
798
- // You can use those to dynamically determine a path to navigate to
799
- return "/some-path";
800
- }
641
+ ## Router Config Reference
801
642
 
802
- // Navigating to /redirect will redirect you to the result of getPath
803
- <Route path="/redirect" component={() => <Navigate href={getPath} />} />;
643
+ ```tsx
644
+ createRouter(config);
804
645
  ```
805
646
 
806
- ### `<Route>`
647
+ | option | type | description |
648
+ | --------------- | -------------------------- | ------------------------------------------------------------------------------------------------------ |
649
+ | `routes` | `RouteDefinition[]` | The route tree — inline arrays infer literally; wrap extracted trees in `defineRoutes` |
650
+ | `base` | `string` | Base url to use for matching routes |
651
+ | `preload` | `RoutePreloadFunc` | App-wide preload: once per mount/request, result reaches the root render-prop as `props.data` |
652
+ | `history` | `RouterHistory` | History adapter; defaults to browser history on the client and the request URL on the server |
653
+ | `singleFlight` | `boolean` | Single-flight mutations, default `true` |
654
+ | `actionBase` | `string` | Root url for server actions, default `/_server` |
655
+ | `preloadLinks` | `boolean` | Preload route code/data on link hover and focus, default `true` |
656
+ | `explicitLinks` | `boolean` | Require the `link` attribute for router handling instead of intercepting all anchors, default `false` |
657
+ | `transformUrl` | `(url: string) => string` | Rewrite URLs before matching |
807
658
 
808
- The Component for defining Routes:
659
+ The returned instance is the provider component and carries the static surface:
809
660
 
810
- | prop | type | description |
811
- | ------------ | ------------------ | ----------------------------------------------------------------- |
812
- | path | string | Path partial for defining the route segment |
813
- | component | `Component` | Component that will be rendered for the matched segment |
814
- | matchFilters | `MatchFilters` | Additional constraints for matching against the route |
815
- | children | `JSX.Element` | Nested `<Route>` definitions |
816
- | preload | `RoutePreloadFunc` | Function called during preload or when the route is navigated to. |
661
+ | member | description |
662
+ | --------- | ------------------------------------------------------------------------------------------------ |
663
+ | `paths` | The [typed path proxy](#typed-paths) |
664
+ | `match` | Pure matching against an arbitrary URL — no rendering or request context; root→leaf, `[]` if none |
665
+ | `routes` | The config tree |
666
+ | `config` | The full config — lets server integrations consume the instance directly |
817
667
 
818
668
  ## Router Primitives
819
669
 
820
- Solid Router provides a number of primitives that read off the Router and Route context.
670
+ Hooks read the live session off router context.
821
671
 
822
672
  ### useParams
823
673
 
824
- Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
674
+ Retrieves a reactive, store-like object of the current route's path parameters. Pass a paths node for typing:
825
675
 
826
- ```js
827
- const params = useParams();
828
-
829
- // fetch user based on the id path parameter
830
- const [user] = createResource(() => params.id, fetchUser);
676
+ ```tsx
677
+ const params = useParams(); // Params (strings)
678
+ const params = useParams(paths.users); // { id: string } — typed from the tree
831
679
  ```
832
680
 
833
- ### useNavigate
681
+ Inside a route's own `component`/`preload`, [`defineRoute`](#typed-route-params) types `props.params` without a witness.
834
682
 
835
- Retrieves method to do navigation. The method accepts a path to navigate to and an optional object with the following options:
683
+ ### useNavigate
836
684
 
837
- - resolve (_boolean_, default `true`): resolve the path against the current route
838
- - replace (_boolean_, default `false`): replace the history entry
839
- - scroll (_boolean_, default `true`): scroll to top after navigation
840
- - state (_any_, default `undefined`): pass custom state to `location.state`
685
+ Retrieves a method to navigate. Accepts a string or a typed path node, plus options:
841
686
 
842
- **Note:** The state is serialized using the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) which does not support all object types.
687
+ - `resolve` (_boolean_, default `true`): resolve the path against the current route
688
+ - `replace` (_boolean_, default `false`): replace the history entry
689
+ - `scroll` (_boolean_, default `true`): scroll to top after navigation
690
+ - `state` (_any_): pass custom state to `location.state` (serialized with [structured clone](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm))
843
691
 
844
- ```js
692
+ ```tsx
845
693
  const navigate = useNavigate();
846
-
847
- if (unauthorized) {
848
- navigate("/login", { replace: true });
849
- }
694
+ navigate(paths.login, { replace: true });
850
695
  ```
851
696
 
697
+ For declarative redirects on render (the old `<Navigate>`), call it during component setup or redirect from a preload.
698
+
852
699
  ### useLocation
853
700
 
854
- Retrieves reactive `location` object useful for getting things like `pathname`.
701
+ Retrieves the reactive `location` object:
855
702
 
856
- ```js
703
+ ```tsx
857
704
  const location = useLocation();
858
-
859
705
  const pathname = createMemo(() => parsePath(location.pathname));
860
706
  ```
861
707
 
862
708
  ### useSearchParams
863
709
 
864
- Retrieves a tuple containing a reactive object to read the current location's query parameters and a method to update them. The object is a proxy so you must access properties to subscribe to reactive updates. Note values will be strings and property names will retain their casing.
865
-
866
- The setter method accepts an object whose entries will be merged into the current query string. Values `''`, `undefined` and `null` will remove the key from the resulting query string. Updates will behave just like a navigation and the setter accepts the same optional second parameter as `navigate` and auto-scrolling is disabled by default.
867
-
868
- ```js
869
- const [searchParams, setSearchParams] = useSearchParams();
870
-
871
- return (
872
- <div>
873
- <span>Page: {searchParams.page}</span>
874
- <button
875
- onClick={() =>
876
- setSearchParams({ page: (parseInt(searchParams.page) || 0) + 1 })
877
- }
878
- >
879
- Next Page
880
- </button>
881
- </div>
882
- );
883
- ```
710
+ See [Typed Search Params](#typed-search-params). Reads are proxied — access properties to subscribe.
884
711
 
885
712
  ### useIsRouting
886
713
 
887
- Retrieves signal that indicates whether the route is currently in a Transition. Useful for showing stale/pending state when the route resolution is Suspended during concurrent rendering.
714
+ A signal indicating whether the router is processing a navigation — useful for pending UI while the next route and its data settle:
888
715
 
889
- ```js
716
+ ```tsx
890
717
  const isRouting = useIsRouting();
891
-
892
- return (
893
- <div classList={{ "grey-out": isRouting() }}>
894
- <MyAwesomeContent />
895
- </div>
896
- );
718
+ return <div classList={{ "grey-out": isRouting() }}>...</div>;
897
719
  ```
898
720
 
899
721
  ### useMatch
900
722
 
901
- `useMatch` takes an accessor that returns the path and creates a Memo that returns match information if the current path matches the provided path. Useful for determining if a given path matches the current route.
723
+ Tests a path *pattern you supply* against the current location; returns a memo of match information or `undefined`. It never consults the route tree — the pattern doesn't have to correspond to a defined route. The match's `params` are typed from the pattern, and a typed path node works too (a concrete URL — useful for "am I here" checks):
902
724
 
903
- ```js
904
- const match = useMatch(() => props.href);
725
+ ```tsx
726
+ const match = useMatch(() => "/admin/*rest");
727
+ match()?.params.rest; // string
728
+ return <Show when={match()}>...</Show>;
905
729
 
906
- return <div classList={{ active: Boolean(match()) }} />;
730
+ const here = useMatch(() => paths.users(2));
907
731
  ```
908
732
 
909
- ### useCurrentMatches
733
+ ### useRouteMatches
910
734
 
911
- `useCurrentMatches` returns all the matches for the current matched route. Useful for getting all the route information.
735
+ Returns an accessor of the router's *resolved* matches for the current location — the chain of route definitions producing the current render, outermost first. This is the counterpart to `useMatch`: one reflects the route tree, the other tests a pattern. Useful for reading `info` metadata:
912
736
 
913
- For example if you stored breadcrumbs on your route definition you could retrieve them like so:
737
+ ```tsx
738
+ const matches = useRouteMatches();
739
+ const breadcrumbs = createMemo(() => matches().map(m => m.route.info?.breadcrumb));
740
+ ```
914
741
 
915
- ```js
916
- const matches = useCurrentMatches();
742
+ `info` is freeform by default; augment `RouteInfo` to type it app-wide — declared keys are checked at route definitions and typed on reads:
917
743
 
918
- const breadcrumbs = createMemo(() =>
919
- matches().map((m) => m.route.info.breadcrumb)
920
- );
744
+ ```ts
745
+ declare module "@solidjs/router" {
746
+ interface RouteInfo {
747
+ breadcrumb?: string;
748
+ }
749
+ }
921
750
  ```
922
751
 
923
752
  ### usePreloadRoute
924
753
 
925
- `usePreloadRoute` returns a function that can be used to preload a route manual. This is what happens automatically with link hovering and similar focus based behavior, but it is available here as an API.
754
+ Returns a function to preload a route manually — the same work link hover/focus triggers automatically. Accepts strings, URLs, and typed path nodes:
926
755
 
927
- ```js
756
+ ```tsx
928
757
  const preload = usePreloadRoute();
929
-
930
- preload(`/users/settings`, { preloadData: true });
758
+ preload(paths.users(2).settings, { preloadData: true });
931
759
  ```
932
760
 
933
- ### useBeforeLeave
761
+ ### useLinkState
934
762
 
935
- `useBeforeLeave` takes a function that will be called prior to leaving a route. The function will be called with:
763
+ Reactive `active`/`current`/`pending` state for [custom link components](#links).
936
764
 
937
- - from (_Location_): current location (before change).
938
- - to (_string | number_): path passed to `navigate`.
939
- - options (_NavigateOptions_): options passed to `navigate`.
940
- - preventDefault (_function_): call to block the route change.
941
- - defaultPrevented (_readonly boolean_): `true` if any previously called leave handlers called `preventDefault`.
942
- - retry (_function_, _force?: boolean_ ): call to retry the same navigation, perhaps after confirming with the user. Pass `true` to skip running the leave handlers again (i.e. force navigate without confirming).
765
+ ### useBeforeLeave
943
766
 
944
- Example usage:
767
+ Takes a function called before leaving a route. The blocking machinery is installed lazily on first use, so apps that never call `useBeforeLeave` don't pay for it in their bundle. The handler receives:
945
768
 
946
- ```js
769
+ - `from` (_Location_): current location (before change)
770
+ - `to` (_string | number_): path passed to `navigate`
771
+ - `options` (_NavigateOptions_): options passed to `navigate`
772
+ - `preventDefault()`: call to block the route change
773
+ - `defaultPrevented` (_readonly boolean_): `true` if any previous handler called `preventDefault`
774
+ - `retry(force?)`: retry the navigation, e.g. after confirming with the user; pass `true` to skip re-running leave handlers
775
+
776
+ ```tsx
947
777
  useBeforeLeave((e: BeforeLeaveEventArgs) => {
948
778
  if (form.isDirty && !e.defaultPrevented) {
949
- // preventDefault to block immediately and prompt user async
950
779
  e.preventDefault();
951
780
  setTimeout(() => {
952
781
  if (window.confirm("Discard unsaved changes - are you sure?")) {
953
- // user wants to proceed anyway so retry with force=true
954
782
  e.retry(true);
955
783
  }
956
784
  }, 100);
@@ -958,73 +786,154 @@ useBeforeLeave((e: BeforeLeaveEventArgs) => {
958
786
  });
959
787
  ```
960
788
 
961
- ## Migrations from 0.9.x
789
+ ## Other Environments
962
790
 
963
- v0.10.0 brings some big changes to support the future of routing including Islands/Partial Hydration hybrid solutions. Most notably there is no Context API available in non-hydrating parts of the application.
791
+ History adapters are plain imports, so unused ones never enter your bundle:
964
792
 
965
- The biggest changes are around removed APIs that need to be replaced.
793
+ ```tsx
794
+ import { createRouter, hashHistory, memoryHistory } from "@solidjs/router";
966
795
 
967
- ### `<Outlet>`, `<Routes>`, `useRoutes`
796
+ // hash mode
797
+ const Router = createRouter({ routes, history: hashHistory() });
968
798
 
969
- This is no longer used and instead will use `props.children` passed from into the page components for outlets. This keeps the outlet directly passed from its page and avoids oddness of trying to use context across Islands boundaries. Nested `<Routes>` components inherently cause waterfalls and are `<Outlets>` themselves so they have the same concerns.
799
+ // tests and non-browser environments
800
+ const Router = createRouter({ routes, history: memoryHistory("/users/1") });
801
+ ```
970
802
 
971
- Keep in mind no `<Routes>` means the `<Router>` API is different. The `<Router>` acts as the `<Routes>` component and its children can only be `<Route>` components. Your top-level layout should go in the root prop of the router [as shown above](#configure-your-routes)
803
+ ### Environments without Proxy
972
804
 
973
- ## `element` prop removed from `Route`
805
+ On runtimes without `Proxy` support (some older smart TVs), the core router still works: typed `paths` are built lazily so they only require `Proxy` if you access them, and `params`/`location.query` can be swapped to a `Proxy`-free implementation through the history adapter's `paramsWrapper`/`queryWrapper` utils:
974
806
 
975
- Related without Outlet component it has to be passed in manually. At which point the `element` prop has less value. Removing the second way to define route components to reduce confusion and edge cases.
807
+ ```tsx
808
+ const base = browserHistory();
809
+ const history = {
810
+ ...base,
811
+ // wrappers build objects with defined getters instead of a Proxy
812
+ utils: { ...base.utils, paramsWrapper, queryWrapper }
813
+ };
814
+ const Router = createRouter({ routes, history });
815
+ ```
976
816
 
977
- ### `data` functions & `useRouteData`
817
+ On the server the request URL drives rendering automatically. Without a request event (SSG scripts, server-side tests), the configured history adapter provides the location, so `memoryHistory("/page")` renders that page isomorphically.
978
818
 
979
- These have been replaced by a preload mechanism. This allows link hover preloads (as the preload function can be run as much as wanted without worry about reactivity). It support deduping/query APIs which give more control over how things are cached. It also addresses TS issues with getting the right types in the Component without `typeof` checks.
819
+ The instance also matches arbitrary URLs anywhere — server middleware, sitemap generation, tests — with no rendering involved:
980
820
 
981
- That being said you can reproduce the old pattern largely by turning off preloads at the router level and then injecting your own Context:
821
+ ```tsx
822
+ import { Router } from "./router";
982
823
 
983
- ```js
984
- import { lazy } from "solid-js";
985
- import { Route } from "@solidjs/router";
824
+ Router.match("/users/2/settings?tab=x");
825
+ // [
826
+ // { path: "/users/:id", match: "/users/2", params: { id: "2" } },
827
+ // { path: "/settings", match: "/users/2/settings", params: {} }
828
+ // ]
829
+ ```
986
830
 
987
- const User = lazy(() => import("./pages/users/[id].js"));
831
+ ## Server Integration
988
832
 
989
- // preload function
990
- function preloadUser({ params, location }) {
991
- const [user] = createResource(() => params.id, fetchUser);
992
- return user;
993
- }
833
+ Framework handler wiring lives in `@solidjs/router/server`. The integration accepts the router instance directly — its routes, base, and preload are the single source of truth:
994
834
 
995
- // Pass it in the route definition
996
- <Router preload={false}>
997
- <Route path="/users/:id" component={User} preload={preloadUser} />
998
- </Router>;
835
+ ```tsx
836
+ import { createFlightDataCollector } from "@solidjs/router/server";
837
+ import { Router } from "./app/router";
838
+
839
+ const collectFlightData = createFlightDataCollector(Router);
999
840
  ```
1000
841
 
1001
- And then in your component taking the page props and putting them in a Context.
842
+ `createFlightDataCollector` produces the single-flight hook: after a mutation it reruns the route data the mutation invalidated for the page the client is on (or is redirected to), folding fresh data into the same response. This policy previously lived inside SolidStart; the router now owns it, so custom server setups get single-flight mutations without a framework.
1002
843
 
1003
- ```js
1004
- function User(props) {
1005
- <UserContext.Provider value={props.data}>
1006
- {/* my component content */}
1007
- </UserContext.Provider>;
1008
- }
844
+ The no-JS form convention needs no wiring at all: the server function runtime answers form posts made without the client runtime by redirecting back with the outcome in a one-shot flash cookie, and the router's SSR reads it into submission state. To configure it (e.g. a base path), pass `createNoJSHandler(options)` from `@solidjs/web/server-functions/server` as the handler's `handleNoJS`.
1009
845
 
1010
- // Somewhere else
1011
- function UserDetails() {
1012
- const user = useContext(UserContext);
1013
- // render stuff
1014
- }
846
+ ## Migration from 0.x
847
+
848
+ This guide maps from the stable 0.x releases (Solid 1). 1.0 removes the component-based API — the `createRouter` factory is the only way to set up the router, and plain `<a>` elements are the only link primitive. 1.0 also targets Solid 2, so the async data patterns change alongside the router API.
849
+
850
+ ### Router components → `createRouter`
851
+
852
+ ```tsx
853
+ // 0.x
854
+ <Router root={App}>
855
+ <Route path="/users" component={Users} />
856
+ <Route path="/users/:id" component={User} />
857
+ </Router>
858
+
859
+ // 1.0
860
+ const Router = createRouter({
861
+ routes: [
862
+ { path: "/users", component: Users },
863
+ { path: "/users/:id", component: User }
864
+ ]
865
+ });
866
+
867
+ <Router>{props => <App {...props} />}</Router>
868
+ ```
869
+
870
+ - `<HashRouter>` → `createRouter({ routes, history: hashHistory() })`
871
+ - `<MemoryRouter>` / `createMemoryHistory` → `createRouter({ routes, history: memoryHistory("/initial") })`
872
+ - `<StaticRouter url>` / `<Router url>` for SSR → automatic from the request URL; without a request event, pass `memoryHistory(url)`
873
+ - `root` prop → the render-prop child; `rootPreload` → the factory's `preload` option
874
+
875
+ ### JSX `<Route>` → config objects
876
+
877
+ Route props map 1:1 onto definition keys (`path`, `component`, `preload`, `matchFilters`, `info`); nesting becomes `children` arrays. Wrap extracted route trees in `defineRoutes` to get typed `paths`. File-based routing generates config.
878
+
879
+ ### `<A>` → plain `<a>`
880
+
881
+ - `<A href replace noScroll state>` → `<a href replace noscroll state>` (attributes, all lowercase)
882
+ - `activeClass` / `inactiveClass` → CSS attribute selectors on `[data-active]` / `[aria-current="page"]`
883
+ - `end` → style exact matches with `[aria-current="page"]` instead of `[data-active]`; the root path already only matches exactly
884
+ - Route-relative hrefs → typed `paths`; `useResolvedPath` / `useHref` remain for manual resolution
885
+ - Custom link components → `useLinkState`
886
+
887
+ ### Removed and renamed
888
+
889
+ - `<Navigate>` → call `useNavigate()` during component setup, or redirect from a preload
890
+ - `useCurrentMatches` → `useRouteMatches` (same behavior)
891
+ - `redirect` / `reload` → import from `@solidjs/web`; they're protocol-level and work without the router
892
+ - `json(data, init)` → `respond(data, init)` from `@solidjs/web`
893
+ - `cache` (deprecated alias) → `query`
894
+
895
+ ### Data APIs (Solid 2)
896
+
897
+ - `createAsync` / `createAsyncStore` are gone — read `query()` results with Solid 2 primitives: `createMemo`, `createProjection`, `createOptimistic`, `createOptimisticStore`.
898
+
899
+ ```tsx
900
+ // 0.x
901
+ const user = createAsync(() => getUser(params.id));
902
+
903
+ // 1.0
904
+ const user = createMemo(() => getUser(params.id));
1015
905
  ```
1016
906
 
907
+ - `query()` stays the source of truth for cached reads and invalidation.
908
+ - `useSubmission` (singular) is gone, and submissions are now settled history rather than in-flight state. Pending/optimistic UI moves to Solid's optimistic primitives fed by the action's `.onSubmit(...)` hook; read settled results with `useSubmissions()` and select the latest with `.at(-1)`.
909
+
910
+ ```tsx
911
+ // 0.x — read in-flight state off the submission
912
+ const submitting = useSubmission(addTodo);
913
+ <span>{submitting.pending && "Saving..."}</span>;
914
+
915
+ // 1.0 — optimistic primitives own in-flight state
916
+ const [todos, setTodos] = createOptimisticStore(() => getTodos(), []);
917
+ const addTodo = action(saveTodo).onSubmit(todo =>
918
+ setTodos(items => {
919
+ items.push({ ...todo, pending: true });
920
+ })
921
+ );
922
+ ```
923
+
924
+ - Action lifecycle centers on instance methods: `.onSubmit(...)` for owner-scoped optimistic work, `.onSettled(...)` for observing completions. Returned values are the expected result channel; thrown errors land on `Submission.error`.
925
+
1017
926
  ## SPAs in Deployed Environments
1018
927
 
1019
- When deploying applications that use a client side router that does not rely on Server Side Rendering you need to handle redirects to your index page so that loading from other URLs does not cause your CDN or Hosting to return not found for pages that aren't actually there.
928
+ When deploying a client-side-routed application without server-side rendering, you need to handle redirects to your index page so that loading other URLs doesn't return a 404.
1020
929
 
1021
- Each provider has a different way of doing this. For example on Netlify you create a `_redirects` file that contains:
930
+ On Netlify, create a `_redirects` file:
1022
931
 
1023
932
  ```sh
1024
933
  /* /index.html 200
1025
934
  ```
1026
935
 
1027
- On Vercel you add a rewrites section to your `vercel.json`:
936
+ On Vercel, add a rewrites section to `vercel.json`:
1028
937
 
1029
938
  ```json
1030
939
  {