@solidjs/router 1.0.0-next.8 → 1.0.0

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 (58) hide show
  1. package/README.md +715 -531
  2. package/dist/components.d.ts +31 -0
  3. package/dist/components.jsx +40 -0
  4. package/dist/data/action.d.ts +11 -40
  5. package/dist/data/action.js +100 -274
  6. package/dist/data/createAsync.d.ts +32 -0
  7. package/dist/data/createAsync.js +96 -0
  8. package/dist/data/events.d.ts +0 -8
  9. package/dist/data/events.js +23 -24
  10. package/dist/data/index.d.ts +4 -2
  11. package/dist/data/index.js +4 -2
  12. package/dist/data/query.d.ts +3 -1
  13. package/dist/data/query.js +22 -39
  14. package/dist/data/response.d.ts +4 -0
  15. package/dist/data/response.js +42 -0
  16. package/dist/index.d.ts +3 -5
  17. package/dist/index.js +1024 -1578
  18. package/dist/index.jsx +2 -2
  19. package/dist/lifecycle.d.ts +4 -29
  20. package/dist/lifecycle.js +37 -40
  21. package/dist/routers/HashRouter.d.ts +9 -0
  22. package/dist/routers/HashRouter.js +41 -0
  23. package/dist/routers/MemoryRouter.d.ts +24 -0
  24. package/dist/routers/MemoryRouter.js +57 -0
  25. package/dist/routers/Router.d.ts +17 -0
  26. package/dist/routers/Router.js +59 -0
  27. package/dist/routers/StaticRouter.d.ts +6 -0
  28. package/dist/routers/StaticRouter.js +15 -0
  29. package/dist/routers/components.d.ts +26 -12
  30. package/dist/routers/components.jsx +54 -68
  31. package/dist/routers/createRouter.d.ts +10 -0
  32. package/dist/routers/createRouter.js +41 -0
  33. package/dist/routers/index.d.ts +11 -4
  34. package/dist/routers/index.js +6 -2
  35. package/dist/routers/scrollRestoration.d.ts +32 -0
  36. package/dist/routers/scrollRestoration.js +96 -0
  37. package/dist/routing.d.ts +52 -88
  38. package/dist/routing.js +166 -381
  39. package/dist/types.d.ts +33 -83
  40. package/dist/utils.d.ts +0 -2
  41. package/dist/utils.js +0 -2
  42. package/package.json +7 -11
  43. package/dist/claims.d.ts +0 -21
  44. package/dist/claims.js +0 -115
  45. package/dist/data/flash.d.ts +0 -21
  46. package/dist/data/flash.js +0 -78
  47. package/dist/data/flashCookie.d.ts +0 -7
  48. package/dist/data/flashCookie.js +0 -20
  49. package/dist/data/serverForms.d.ts +0 -1
  50. package/dist/data/serverForms.js +0 -5
  51. package/dist/paths.d.ts +0 -117
  52. package/dist/paths.js +0 -41
  53. package/dist/routers/factory.d.ts +0 -45
  54. package/dist/routers/factory.jsx +0 -143
  55. package/dist/routers/history.d.ts +0 -24
  56. package/dist/routers/history.js +0 -180
  57. package/dist/server.d.ts +0 -75
  58. package/dist/server.js +0 -264
package/README.md CHANGED
@@ -10,683 +10,947 @@
10
10
 
11
11
  </div>
12
12
 
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.
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.
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
- - **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"`
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
25
30
 
26
- ## Table of Contents
31
+ ## Table of contents
27
32
 
28
33
  - [Getting Started](#getting-started)
29
- - [The Mental Model: Instance vs Hooks](#the-mental-model-instance-vs-hooks)
30
- - [Route Definitions](#route-definitions)
31
- - [Dynamic Routes](#dynamic-routes)
32
- - [Match Filters](#match-filters)
33
- - [Optional Parameters](#optional-parameters)
34
- - [Wildcard Routes](#wildcard-routes)
35
- - [Multiple Paths](#multiple-paths)
36
- - [Nested Routes](#nested-routes)
37
- - [Lazy Route Subtrees](#lazy-route-subtrees)
38
- - [Typed Paths](#typed-paths)
39
- - [Links](#links)
40
- - [Preload Functions](#preload-functions)
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)
41
41
  - [Data APIs](#data-apis)
42
- - [Typed Search Params](#typed-search-params)
43
- - [Router Config Reference](#router-config-reference)
42
+ - [Config Based Routing](#config-based-routing)
43
+ - [Components](#components)
44
44
  - [Router Primitives](#router-primitives)
45
- - [Other Environments](#other-environments)
46
- - [Server Integration](#server-integration)
47
- - [Migration from 0.x](#migration-from-0x)
45
+ - [useParams](#useparams)
46
+ - [useNavigate](#usenavigate)
47
+ - [useLocation](#uselocation)
48
+ - [useSearchParams](#usesearchparams)
49
+ - [useIsRouting](#useisrouting)
50
+ - [useMatch](#usematch)
51
+ - [useCurrentMatches](#useCurrentMatches)
52
+ - [useBeforeLeave](#usebeforeleave)
48
53
  - [SPAs in Deployed Environments](#spas-in-deployed-environments)
49
54
 
50
55
  ## Getting Started
51
56
 
57
+ ### Set Up the Router
58
+
52
59
  ```bash
53
60
  # use preferred package manager
54
61
  npm add @solidjs/router
55
62
  ```
56
63
 
57
- 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:
58
-
59
- ```tsx
60
- // app/router.ts
61
- import { lazy } from "solid-js";
62
- import { createRouter } from "@solidjs/router";
64
+ Install `@solidjs/router`, then start your application by rendering the router component
63
65
 
64
- export const Router = createRouter({
65
- routes: [
66
- { path: "/", component: lazy(() => import("./pages/Home")) },
67
- { path: "/about", component: lazy(() => import("./pages/About")) },
68
- {
69
- path: "/users/:id",
70
- component: lazy(() => import("./pages/User")),
71
- children: [
72
- { path: "/", component: lazy(() => import("./pages/UserOverview")) },
73
- { path: "/settings", component: lazy(() => import("./pages/UserSettings")) }
74
- ]
75
- },
76
- { path: "*404", component: lazy(() => import("./pages/NotFound")) }
77
- ]
78
- });
66
+ ```jsx
67
+ import { render } from "solid-js/web";
68
+ import { Router } from "@solidjs/router";
79
69
 
80
- export const { paths } = Router;
70
+ render(() => <Router />, document.getElementById("app"));
81
71
  ```
82
72
 
83
- 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.
73
+ This sets up a Router that will match on the url to display the desired page
84
74
 
85
- 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:
75
+ ### Configure Your Routes
86
76
 
87
- ```tsx
88
- // features/admin/routes.ts
89
- export const adminRoutes = defineRoutes([
90
- { path: "/admin", component: Admin, children: [/* ... */] }
91
- ]);
77
+ Solid Router allows you to configure your routes using JSX:
92
78
 
93
- // app/router.ts
94
- export const Router = createRouter({ routes: [...appRoutes, ...adminRoutes] });
95
- ```
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.
96
80
 
97
- 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:
81
+ ```jsx
82
+ import { render } from "solid-js/web";
83
+ import { Router, Route } from "@solidjs/router";
98
84
 
99
- ```tsx
100
- // app/index.tsx
101
- import { render } from "@solidjs/web";
102
- import { Router } from "./router";
85
+ import Home from "./pages/Home";
86
+ import Users from "./pages/Users";
103
87
 
104
88
  render(
105
89
  () => (
106
90
  <Router>
107
- {props => (
108
- <>
109
- <h1>My Site with lots of pages</h1>
110
- {props.children}
111
- </>
112
- )}
91
+ <Route path="/users" component={Users} />
92
+ <Route path="/" component={Home} />
113
93
  </Router>
114
94
  ),
115
- document.getElementById("app")!
95
+ document.getElementById("app")
116
96
  );
117
97
  ```
118
98
 
119
- Links are plain anchors. Typed path nodes coerce to strings on the attribute, and the router intercepts clicks through delegation:
99
+ 2. Provide a root level layout
100
+
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
120
102
 
121
- ```tsx
122
- import { paths } from "./router";
103
+ ```jsx
104
+ import { render } from "solid-js/web";
105
+ import { Router, Route } from "@solidjs/router";
123
106
 
124
- <nav>
125
- <a href={paths()}>Home</a>
126
- <a href={paths.about}>About</a>
127
- <a href={paths.users(user.id).settings}>Settings</a>
128
- </nav>;
107
+ import Home from "./pages/Home";
108
+ import Users from "./pages/Users";
109
+
110
+ const App = (props) => (
111
+ <>
112
+ <h1>My Site with lots of pages</h1>
113
+ {props.children}
114
+ </>
115
+ );
116
+
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
+ );
129
126
  ```
130
127
 
131
- ## The Mental Model: Instance vs Hooks
128
+ 3. Create a catch-all route (404 page)
132
129
 
133
- 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.**
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.
134
131
 
135
- 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.
132
+ ```jsx
133
+ import { render } from "solid-js/web";
134
+ import { Router, Route } from "@solidjs/router";
136
135
 
137
- | Instance — facts about the *app* | Hooks — facts about the *session* |
138
- | --- | --- |
139
- | `paths` — how to spell URLs | `useLocation`, `useParams` — where am I |
140
- | `match(url)` — how would a URL match | `useNavigate`, `usePreloadRoute` — move / warm |
141
- | `routes`, `config` — what exists | `useIsRouting`, `useRouteMatches`, `useSearchParams` — live state |
136
+ import Home from "./pages/Home";
137
+ import Users from "./pages/Users";
138
+ import NotFound from "./pages/404";
142
139
 
143
- They compose as noun and verb — the instance supplies a typed URL, the hook acts on the current session:
140
+ const App = (props) => (
141
+ <>
142
+ <h1>My Site with lots of pages</h1>
143
+ {props.children}
144
+ </>
145
+ );
144
146
 
145
- ```tsx
146
- const navigate = useNavigate();
147
- navigate(paths.users(2)); // verb(noun)
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
+
159
+ 4. Lazy-load route components
160
+
161
+ This way, the `Users` and `Home` components will only be loaded if you're navigating to `/users` or `/`, respectively.
162
+
163
+ ```jsx
164
+ import { lazy } from "solid-js";
165
+ import { render } from "solid-js/web";
166
+ import { Router, Route } from "@solidjs/router";
167
+
168
+ const Users = lazy(() => import("./pages/Users"));
169
+ const Home = lazy(() => import("./pages/Home"));
170
+
171
+ const App = (props) => (
172
+ <>
173
+ <h1>My Site with lots of pages</h1>
174
+ {props.children}
175
+ </>
176
+ );
148
177
 
149
- const params = useParams(paths.users); // hook, typed by the instance
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
+ );
150
187
  ```
151
188
 
152
- **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.
189
+ ### Create Links to Your Routes
153
190
 
154
- ## Route Definitions
191
+ Use an anchor tag that takes you to a route:
155
192
 
156
- A route definition supports:
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
+ );
211
+
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
+ );
221
+ ```
157
222
 
158
- | key | type | description |
159
- | -------------- | --------------------------------------- | ------------------------------------------------------------------ |
160
- | `path` | `string \| string[]` | Path partial for this route segment |
161
- | `component` | `Component` | Component rendered for the matched segment |
162
- | `children` | `RouteDefinition \| RouteDefinition[] \| () => Promise<...>` | Nested route definitions, or a thunk for a [lazy subtree](#lazy-route-subtrees) |
163
- | `preload` | `RoutePreloadFunc` | Called on preload intent (hover/focus) and navigation |
164
- | `matchFilters` | `MatchFilters` | Additional constraints for matching parameters |
165
- | `search` | `StandardSchemaV1` | Search-param validator; its types flow into `paths` and hooks |
166
- | `info` | `Record<string, any>` | Arbitrary metadata, readable via `useRouteMatches` |
223
+ ## Dynamic Routes
167
224
 
168
- 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.
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.
169
226
 
170
- ### Dynamic Routes
227
+ ```jsx
228
+ import { lazy } from "solid-js";
229
+ import { render } from "solid-js/web";
230
+ import { Router, Route } from "@solidjs/router";
171
231
 
172
- Treat part of the path as a parameter with a colon:
232
+ const Users = lazy(() => import("./pages/Users"));
233
+ const User = lazy(() => import("./pages/User"));
234
+ const Home = lazy(() => import("./pages/Home"));
173
235
 
174
- ```tsx
175
- const routes = defineRoutes([
176
- { path: "/users", component: Users },
177
- { path: "/users/:id", component: User }
178
- ]);
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
+ );
179
246
  ```
180
247
 
181
- As long as the URL fits the pattern, the `User` component shows, and `id` is available via `useParams`.
248
+ The colon indicates that `id` can be any string, and as long as the URL fits that pattern, the `User` component will show.
249
+
250
+ You can then access that `id` from within a route component with `useParams`.
182
251
 
183
- **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>`:
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:
184
254
 
185
- ```tsx
255
+ ```jsx
186
256
  <Show when={params.something} keyed>
187
257
  <MyComponent />
188
258
  </Show>
189
259
  ```
190
260
 
191
- ### Match Filters
261
+ ---
192
262
 
193
- Each parameter can be validated with a `MatchFilter` — an enum array, a regex, or a predicate. If validation fails, the route doesn't match:
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.
194
265
 
195
- ```tsx
196
- import { int, type MatchFilters } from "@solidjs/router";
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";
271
+
272
+ const User = lazy(() => import("./pages/User"));
197
273
 
198
274
  const filters: MatchFilters = {
199
- parent: ["mom", "dad"], // enum values
200
- id: /^\d+$/, // only numbers
201
- withHtmlExtension: (v: string) => v.length > 5 && v.endsWith(".html")
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
202
278
  };
203
279
 
204
- const routes = defineRoutes([
205
- { path: "/users/:parent/:id/:withHtmlExtension", component: User, matchFilters: filters }
206
- ]);
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
+ );
207
292
  ```
208
293
 
209
- 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.
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.
210
296
 
211
- The built-in `int` filter is *typed*: it constrains matching to integers at runtime and types the param as `number` at `paths` callsites:
297
+ So in this example:
212
298
 
213
- ```tsx
214
- { path: "/users/:id", matchFilters: { id: int }, component: User }
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`.
215
304
 
216
- paths.users(123); // ok
217
- paths.users("abc"); // type error
218
- ```
305
+ ---
219
306
 
220
307
  ### Optional Parameters
221
308
 
222
- Add a question mark to make a parameter optional:
309
+ Parameters can be specified as optional by adding a question mark to the end of the parameter name:
223
310
 
224
- ```tsx
311
+ ```jsx
225
312
  // Matches stories and stories/123 but not stories/123/comments
226
- { path: "/stories/:id?", component: Stories }
313
+ <Route path="/stories/:id?" component={Stories} />
227
314
  ```
228
315
 
229
316
  ### Wildcard Routes
230
317
 
231
- Use `*` to match any remainder of the path, optionally naming it to expose it as a parameter:
318
+ `:param` lets you match an arbitrary name at that point in the path. You can use `*` to match any end of the path:
319
+
320
+ ```jsx
321
+ // Matches any path that begins with foo, including foo/, foo/a/, foo/a/b/c
322
+ <Route path="foo/*" component={Foo} />
323
+ ```
324
+
325
+ If you want to expose the wild part of the path to the component as a parameter, you can name it:
232
326
 
233
- ```tsx
234
- { path: "foo/*", component: Foo } // matches foo/, foo/a, foo/a/b/c
235
- { path: "foo/*any", component: Foo } // rest of the path available as params.any
327
+ ```jsx
328
+ <Route path="foo/*any" component={Foo} />
236
329
  ```
237
330
 
238
- The wildcard token must be the last part of the path; `foo/*any/bar` won't create any routes.
331
+ Note that the wildcard token must be the last part of the path; `foo/*any/bar` won't create any routes.
239
332
 
240
333
  ### Multiple Paths
241
334
 
242
- An array of paths lets a route stay mounted (no re-render) when switching between locations it matches:
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:
243
336
 
244
- ```tsx
245
- // Navigating from login to register does not re-render Login
246
- { path: ["login", "register"], component: Login }
337
+ ```jsx
338
+ // Navigating from login to register does not cause the Login component to re-render
339
+ <Route path={["login", "register"]} component={Login} />
247
340
  ```
248
341
 
249
- ### Nested Routes
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
+ ```
349
+
350
+ ```jsx
351
+ <Route path="/users">
352
+ <Route path="/:id" component={User} />
353
+ </Route>
354
+ ```
355
+
356
+ `/users/:id` renders the `<User/>` component, and `/users/` is an empty route.
357
+
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:
359
+
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
+ ```
250
376
 
251
- Only leaf nodes become routes. A parent with a `component` wraps its children, which render where the parent places `props.children`:
377
+ You can also take advantage of nesting by using `props.children` passed to the route component.
252
378
 
253
- ```tsx
379
+ ```jsx
254
380
  function PageWrapper(props) {
255
381
  return (
256
382
  <div>
257
- <h1>We love our users!</h1>
383
+ <h1> We love our users! </h1>
258
384
  {props.children}
259
- <a href={paths()}>Back Home</a>
385
+ <A href="/">Back Home</A>
260
386
  </div>
261
387
  );
262
388
  }
263
389
 
264
- const routes = defineRoutes([
265
- {
266
- path: "/users",
267
- component: PageWrapper,
268
- children: [
269
- { path: "/", component: Users },
270
- { path: "/:id", component: User }
271
- ]
272
- }
273
- ]);
390
+ <Route path="/users" component={PageWrapper}>
391
+ <Route path="/" component={Users} />
392
+ <Route path="/:id" component={User} />
393
+ </Route>;
274
394
  ```
275
395
 
276
- You can nest indefinitely. In this example the only route created is `/layer1/layer2`, rendered as three nested divs:
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>
412
+ ```
277
413
 
278
- ```tsx
279
- {
280
- path: "/",
281
- component: props => <div>Onion starts here {props.children}</div>,
282
- children: [{
283
- path: "layer1",
284
- component: props => <div>Another layer {props.children}</div>,
285
- children: [{ path: "layer2", component: () => <div>Innermost layer</div> }]
286
- }]
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"));
425
+
426
+ // preload function
427
+ function preloadUser({ params, location }) {
428
+ // do preloading
287
429
  }
430
+
431
+ // Pass it in the route definition
432
+ <Route path="/users/:id" component={User} preload={preloadUser} />;
288
433
  ```
289
434
 
290
- ### Lazy Route Subtrees
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> |
291
440
 
292
- `children` also accepts a thunk, so a whole section's route table (not just its components) stays out of the initial bundle:
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.
293
442
 
294
- ```tsx
295
- // admin/routes.ts
296
- export default defineRoutes([
297
- { path: "/", component: lazy(() => import("./Dashboard")) },
298
- { path: "/users/:id", matchFilters: { id: int }, component: lazy(() => import("./User")) }
299
- ]);
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"));
300
448
 
301
- // app.ts
302
- const router = createRouter({
303
- routes: [
304
- { path: "/", component: Home },
305
- { path: "/admin", component: AdminShell, children: () => import("./admin/routes") }
306
- ]
307
- });
449
+ // In the Route definition
450
+ <Route path="/users/:id" component={User} preload={preloadUser} />;
308
451
  ```
309
452
 
310
- 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:
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:
311
454
 
312
- - **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.
313
- - **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.
314
- - **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.
315
- - **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.
455
+ ## Data APIs
316
456
 
317
- 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.
457
+ Keep in mind that these are entirely optional, but they demonstrate the power of our preload mechanism.
318
458
 
319
- ## Typed Paths
459
+ ### `query`
320
460
 
321
- `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:
461
+ To prevent duplicate fetching and to handle refetching triggers, we provide a query API that accepts a function and returns the same function.
322
462
 
323
- ```tsx
324
- paths.users(123) // ok — matchFilters flow into the callsite
325
- paths.users(2).settings // chainable into children
326
- paths.users(2, { tab: "x" }, "comments") // "/users/2?tab=x#comments"
327
- paths.about() // zero-arg/search calls terminate to a plain string
328
- paths() // "/" — the root
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
329
467
  ```
330
468
 
331
- 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.
469
+ It is expected that the arguments to the query function are serializable.
332
470
 
333
- ## Links
471
+ This query accomplishes the following:
334
472
 
335
- 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.
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.
336
477
 
337
- Behavior modifiers are attributes, so they work identically in client, server-rendered, and third-party markup:
478
+ Using it with preload function might look like:
338
479
 
339
- | attribute | description |
340
- | ---------- | ------------------------------------------------------------------------------ |
341
- | `replace` | Replace the history entry instead of pushing |
342
- | `noscroll` | Turn off scrolling to the top after navigation |
343
- | `state` | JSON string [pushed](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) onto the history stack |
344
- | `preload` | Set to `"false"` to opt this link out of hover/focus preloading |
345
- | `link` | Marks a router link when `explicitLinks` is enabled |
346
- | `target` | Any value (e.g. `_self`) opts the anchor out of router handling |
480
+ ```js
481
+ import { lazy } from "solid-js";
482
+ import { Route } from "@solidjs/router";
483
+ import { getUser } from ... // the query function
347
484
 
348
- ```tsx
349
- <a href={paths.login} replace>Log in</a>
350
- <a href={paths.docs} noscroll>Docs</a>
351
- <a href="https://example.com">External — untouched</a>
352
- ```
485
+ const User = lazy(() => import("./pages/users/[id].js"));
353
486
 
354
- Active and pending state is styled with CSS — one vocabulary for every kind of link:
487
+ // preload function
488
+ function preloadUser({params, location}) {
489
+ void getUser(params.id)
490
+ }
355
491
 
356
- ```css
357
- nav a[aria-current="page"] { font-weight: 600; } /* exact match */
358
- nav a[data-active] { color: var(--accent); } /* exact or prefix match */
359
- a[data-pending] { opacity: 0.6; } /* target of in-flight navigation */
492
+ // Pass it in the route definition
493
+ <Route path="/users/:id" component={User} preload={preloadUser} />;
360
494
  ```
361
495
 
362
- (The root path only ever matches exactly, so `href={paths()}` doesn't light up on every page.)
363
-
364
- For component-library links that need reactive state beyond CSS, `useLinkState` is the programmatic counterpart of the attribute vocabulary:
496
+ Inside your page component you:
365
497
 
366
- ```tsx
367
- import { useLinkState } from "@solidjs/router";
498
+ ```jsx
499
+ // pages/users/[id].js
500
+ import { getUser } from ... // the query function
368
501
 
369
- function TabLink(props: { href: string; children: JSX.Element }) {
370
- const link = useLinkState(() => props.href);
371
- return (
372
- <a href={props.href} class="tab" data-selected={link.active() || undefined}>
373
- {props.children}
374
- </a>
375
- );
502
+ export default function User(props) {
503
+ const user = createAsync(() => getUser(props.params.id));
504
+ return <h1>{user().name}</h1>;
376
505
  }
377
506
  ```
378
507
 
379
- ## Preload Functions
508
+ Cached function has a few useful methods for getting the key that are useful for invalidation.
380
509
 
381
- 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.
510
+ ```ts
511
+ let id = 5;
382
512
 
383
- ```tsx
384
- import { lazy } from "solid-js";
513
+ getUser.key; // returns "users"
514
+ getUser.keyFor(id); // returns "users[5]"
515
+ ```
385
516
 
386
- const User = lazy(() => import("./pages/users/[id].js"));
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`.
387
518
 
388
- function preloadUser({ params, location }) {
389
- void getUser(params.id);
390
- }
519
+ `query` can be defined anywhere and then used inside your components with:
391
520
 
392
- const routes = defineRoutes([{ path: "/users/:id", component: User, preload: preloadUser }]);
393
- ```
521
+ ### `createAsync`
394
522
 
395
- The preload function receives:
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.
396
524
 
397
- | key | type | description |
398
- | -------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
399
- | params | object | The route parameters (same value as `useParams()` inside the route component) |
400
- | location | `{ pathname, search, hash, query, state, key }` | Path information (corresponds to [`useLocation()`](#uselocation)) |
401
- | 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 |
525
+ ```jsx
526
+ const user = createAsync((currentValue) => getUser(params.id));
527
+ ```
402
528
 
403
- 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`.
529
+ It also preserves `latest` field from `createResource`. Note that it will be removed in the future.
404
530
 
405
- ## Data APIs
531
+ ```jsx
532
+ const user = createAsync((currentValue) => getUser(params.id));
533
+ return <h1>{user.latest.name}</h1>;
534
+ ```
406
535
 
407
- These are entirely optional, but they demonstrate the power of the preload mechanism.
536
+ Using `query` in `createResource` directly won't work properly as the fetcher is not reactive and it won't invalidate properly.
408
537
 
409
- ### `query`
538
+ ### `createAsyncStore`
410
539
 
411
- Wrap a fetching function to dedupe calls and participate in revalidation:
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.
412
542
 
413
- ```tsx
414
- const getUser = query(async id => {
415
- return (await fetch(`/api/users/${id}`)).json();
416
- }, "users"); // query key; arguments are serialized alongside it
543
+ ```jsx
544
+ const todos = createAsyncStore(() => getTodos());
417
545
  ```
418
546
 
419
- A query:
547
+ ### `action`
548
+
549
+ Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response helpers can be found below.
420
550
 
421
- 1. Dedupes on the server for the lifetime of the request.
422
- 2. Fills a preload cache in the browser lasting 5 seconds, so hover preloads and route entry share one fetch.
423
- 3. Refetches reactively by key on action revalidation.
424
- 4. Serves as a back/forward cache for browser navigation up to 5 minutes; user-initiated navigation bypasses it.
551
+ ```jsx
552
+ import { action, revalidate, redirect } from "@solidjs/router"
425
553
 
426
- Consume results directly with Solid primitives — there is no router-specific async wrapper:
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
+ });
427
559
 
428
- ```tsx
429
- const user = createMemo(() => getUser(params.id));
430
- return <h1>{user().name}</h1>;
560
+ // in component
561
+ <form action={myAction} method="post" />
431
562
 
432
- // deeply reactive object data
433
- const todos = createProjection(() => getTodos(), []);
563
+ //or
564
+ <button type="submit" formaction={myAction}></button>
434
565
  ```
435
566
 
436
- Keys support targeted invalidation:
567
+ Actions only work with post requests, so make sure to put `method="post"` on your form.
437
568
 
438
- ```ts
439
- getUser.key; // "users"
440
- getUser.keyFor(5); // "users[5]"
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.
570
+
571
+ Picture an action that deletes Todo Item:
572
+
573
+ ```js
574
+ const deleteTodo = action(async (formData: FormData) => {
575
+ const id = Number(formData.get("id"))
576
+ await api.deleteTodo(id)
577
+ })
578
+
579
+ <form action={deleteTodo} method="post">
580
+ <input type="hidden" name="id" value={todo.id} />
581
+ <button type="submit">Delete</button>
582
+ </form>
441
583
  ```
442
584
 
443
- 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.
585
+ Instead with `with` you can write this:
444
586
 
445
- ### `action`
587
+ ```js
588
+ const deleteTodo = action(api.deleteTodo)
446
589
 
447
- 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:
590
+ <form action={deleteTodo.with(todo.id)} method="post">
591
+ <button type="submit">Delete</button>
592
+ </form>
593
+ ```
448
594
 
449
- ```tsx
450
- import { action } from "@solidjs/router";
451
- import { redirect } from "@solidjs/web";
452
- import { paths } from "./router";
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.
453
596
 
454
- const updateUser = action(async (form: FormData) => {
455
- await db.users.update(form.get("id"), form);
456
- throw redirect(paths.users(form.get("id"))); // typed paths work in redirects
457
- });
597
+ #### Notes on `<form>` implementation and SSR
598
+
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");
458
603
  ```
459
604
 
460
- ```tsx
461
- <form action={updateUser} method="post">
462
- <button>Save</button>
463
- </form>
605
+ ### `useAction`
464
606
 
465
- // or
466
- <button type="submit" formaction={updateUser}>Save</button>
607
+ Instead of forms you can use actions directly by wrapping them in a `useAction` primitive. This is how we get the router context.
608
+
609
+ ```jsx
610
+ // in component
611
+ const submit = useAction(myAction);
612
+ submit(...args);
467
613
  ```
468
614
 
469
- 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:
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`
470
618
 
471
- ```css
472
- form[aria-busy] button { pointer-events: none; opacity: 0.6; }
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));
473
633
  ```
474
634
 
475
- 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.
635
+ ### Response Helpers
636
+
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.
476
638
 
477
- 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.)
639
+ #### `redirect(path, options)`
640
+
641
+ Redirects to the next route
642
+
643
+ ```js
644
+ const getUser = query(() => {
645
+ const user = await api.getCurrentUser()
646
+ if (!user) throw redirect("/login");
647
+ return user;
648
+ })
649
+ ```
478
650
 
479
- For optimistic UI, attach owner-scoped hooks to the action and use Solid's optimistic primitives for rendered state:
651
+ #### `reload(options)`
480
652
 
481
- ```tsx
482
- import { createOptimisticStore } from "solid-js";
483
- import { action, query } from "@solidjs/router";
653
+ Reloads the data on the current page
484
654
 
485
- const getTodos = query(async () => fetchTodos(), "todos");
486
- const [todos, setTodos] = createOptimisticStore(() => getTodos(), []);
655
+ ```js
656
+ const getTodo = query(async (id: number) => {
657
+ const todo = await fetchTodo(id);
658
+ return todo;
659
+ }, "todo");
487
660
 
488
- const addTodo = action(async todo => {
489
- await saveTodo(todo);
490
- return { ok: true, todo };
491
- }, "add-todo").onSubmit(todo => {
492
- setTodos(items => {
493
- items.push({ ...todo, pending: true });
494
- });
661
+ const updateTodo = action(async (todo: Todo) => {
662
+ await updateTodo(todo.id, todo);
663
+ reload({ revalidate: getTodo.keyFor(todo.id) });
495
664
  });
496
665
  ```
497
666
 
498
- `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.
667
+ ## Config Based Routing
499
668
 
500
- 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.
669
+ You don't have to use JSX to set up your routes; you can pass an array of route definitions:
501
670
 
502
- Actions have a `with` method (like `bind`) for typed arguments instead of hidden form fields:
671
+ ```jsx
672
+ import { lazy } from "solid-js";
673
+ import { render } from "solid-js/web";
674
+ import { Router } from "@solidjs/router";
503
675
 
504
- ```tsx
505
- const deleteTodo = action(api.deleteTodo);
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
+ ];
506
708
 
507
- <form action={deleteTodo.with(todo.id)} method="post">
508
- <button type="submit">Delete</button>
509
- </form>;
709
+ render(() => <Router>{routes}</Router>, document.getElementById("app"));
510
710
  ```
511
711
 
512
- 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")`.
712
+ Also you can pass a single route definition object for a single route:
513
713
 
514
- ### `useAction`
714
+ ```jsx
715
+ import { lazy } from "solid-js";
716
+ import { render } from "solid-js/web";
717
+ import { Router } from "@solidjs/router";
515
718
 
516
- 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:
719
+ const route = {
720
+ path: "/",
721
+ component: lazy(() => import("/pages/index.js")),
722
+ };
517
723
 
518
- ```tsx
519
- const submit = useAction(myAction);
520
- submit(...args);
724
+ render(() => <Router>{route}</Router>, document.getElementById("app"));
521
725
  ```
522
726
 
523
- ### `useSubmissions`
727
+ ## Alternative Routers
524
728
 
525
- 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:
729
+ ### Hash Mode Router
526
730
 
527
- ```tsx
528
- const submissions = useSubmissions(action, input => filter(input));
529
- const latest = submissions.at(-1);
530
- // { input, result?, error, url, clear(), retry() }
531
- ```
731
+ By default, Solid Router uses `location.pathname` as route path. You can simply switch to hash mode through using `<HashRouter>`.
532
732
 
533
- Use Solid's `createOptimistic` / `createOptimisticStore` for in-flight UI.
733
+ ```jsx
734
+ import { HashRouter } from "@solidjs/router";
534
735
 
535
- ## Typed Search Params
736
+ <HashRouter />;
737
+ ```
536
738
 
537
- 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`:
739
+ ### Memory Mode Router
538
740
 
539
- ```tsx
540
- import * as v from "valibot";
741
+ You can also use memory mode router for testing purpose.
541
742
 
542
- const routes = defineRoutes([
543
- {
544
- path: "/search",
545
- component: Search,
546
- search: v.object({
547
- q: v.optional(v.string(), ""),
548
- page: v.optional(v.pipe(v.unknown(), v.transform(Number)), 1)
549
- })
550
- }
551
- ]);
743
+ ```jsx
744
+ import { MemoryRouter } from "@solidjs/router";
745
+
746
+ <MemoryRouter />;
552
747
  ```
553
748
 
554
- ```tsx
555
- const [search, setSearch] = useSearchParams(paths.search);
556
- search.page; // number (parsed, not "2")
557
- setSearch({ page: search.page + 1 }); // typed setter
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.
558
752
 
559
- <a href={paths.search({ q: "solid", page: 2 })}>Search</a>; // typed builder
753
+ ```jsx
754
+ import { isServer } from "solid-js/web";
755
+ import { Router } from "@solidjs/router";
756
+
757
+ <Router url={isServer ? req.url : ""} />;
560
758
  ```
561
759
 
562
- 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.
760
+ ## Components
761
+
762
+ ### `<Router>`
763
+
764
+ This is the main Router component for the browser.
765
+
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">`.) |
774
+
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.
780
+
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` |
790
+
791
+ ### `<Navigate />`
563
792
 
564
- ## Router Config Reference
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:
565
794
 
566
- ```tsx
567
- createRouter(config);
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
+ }
801
+
802
+ // Navigating to /redirect will redirect you to the result of getPath
803
+ <Route path="/redirect" component={() => <Navigate href={getPath} />} />;
568
804
  ```
569
805
 
570
- | option | type | description |
571
- | --------------- | -------------------------- | ------------------------------------------------------------------------------------------------------ |
572
- | `routes` | `RouteDefinition[]` | The route tree — inline arrays infer literally; wrap extracted trees in `defineRoutes` |
573
- | `base` | `string` | Base url to use for matching routes |
574
- | `preload` | `RoutePreloadFunc` | App-wide preload: once per mount/request, result reaches the root render-prop as `props.data` |
575
- | `history` | `RouterHistory` | History adapter; defaults to browser history on the client and the request URL on the server |
576
- | `singleFlight` | `boolean` | Single-flight mutations, default `true` |
577
- | `actionBase` | `string` | Root url for server actions, default `/_server` |
578
- | `preloadLinks` | `boolean` | Preload route code/data on link hover and focus, default `true` |
579
- | `explicitLinks` | `boolean` | Require the `link` attribute for router handling instead of intercepting all anchors, default `false` |
580
- | `transformUrl` | `(url: string) => string` | Rewrite URLs before matching |
806
+ ### `<Route>`
581
807
 
582
- The returned instance is the provider component and carries the static surface:
808
+ The Component for defining Routes:
583
809
 
584
- | member | description |
585
- | --------- | ------------------------------------------------------------------------------------------------ |
586
- | `paths` | The [typed path proxy](#typed-paths) |
587
- | `match` | Pure matching against an arbitrary URL — no rendering or request context; root→leaf, `[]` if none |
588
- | `routes` | The config tree |
589
- | `config` | The full config — lets server integrations consume the instance directly |
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. |
590
817
 
591
818
  ## Router Primitives
592
819
 
593
- Hooks read the live session off router context.
820
+ Solid Router provides a number of primitives that read off the Router and Route context.
594
821
 
595
822
  ### useParams
596
823
 
597
- Retrieves a reactive, store-like object of the current route's path parameters. Pass a paths node for typing:
824
+ Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
598
825
 
599
- ```tsx
600
- const params = useParams(); // Params (strings)
601
- const params = useParams(paths.users); // { id: number } — typed via matchFilters
826
+ ```js
827
+ const params = useParams();
828
+
829
+ // fetch user based on the id path parameter
830
+ const [user] = createResource(() => params.id, fetchUser);
602
831
  ```
603
832
 
604
833
  ### useNavigate
605
834
 
606
- Retrieves a method to navigate. Accepts a string or a typed path node, plus options:
835
+ Retrieves method to do navigation. The method accepts a path to navigate to and an optional object with the following options:
836
+
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`
607
841
 
608
- - `resolve` (_boolean_, default `true`): resolve the path against the current route
609
- - `replace` (_boolean_, default `false`): replace the history entry
610
- - `scroll` (_boolean_, default `true`): scroll to top after navigation
611
- - `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))
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.
612
843
 
613
- ```tsx
844
+ ```js
614
845
  const navigate = useNavigate();
615
- navigate(paths.login, { replace: true });
616
- ```
617
846
 
618
- For declarative redirects on render (the old `<Navigate>`), call it during component setup or redirect from a preload.
847
+ if (unauthorized) {
848
+ navigate("/login", { replace: true });
849
+ }
850
+ ```
619
851
 
620
852
  ### useLocation
621
853
 
622
- Retrieves the reactive `location` object:
854
+ Retrieves reactive `location` object useful for getting things like `pathname`.
623
855
 
624
- ```tsx
856
+ ```js
625
857
  const location = useLocation();
858
+
626
859
  const pathname = createMemo(() => parsePath(location.pathname));
627
860
  ```
628
861
 
629
862
  ### useSearchParams
630
863
 
631
- See [Typed Search Params](#typed-search-params). Reads are proxied — access properties to subscribe.
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
+ ```
632
884
 
633
885
  ### useIsRouting
634
886
 
635
- A signal indicating whether the router is processing a navigation — useful for pending UI while the next route and its data settle:
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.
636
888
 
637
- ```tsx
889
+ ```js
638
890
  const isRouting = useIsRouting();
639
- return <div classList={{ "grey-out": isRouting() }}>...</div>;
891
+
892
+ return (
893
+ <div classList={{ "grey-out": isRouting() }}>
894
+ <MyAwesomeContent />
895
+ </div>
896
+ );
640
897
  ```
641
898
 
642
899
  ### useMatch
643
900
 
644
- 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:
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.
902
+
903
+ ```js
904
+ const match = useMatch(() => props.href);
645
905
 
646
- ```tsx
647
- const match = useMatch(() => "/admin/*rest");
648
- return <Show when={match()}>...</Show>;
906
+ return <div classList={{ active: Boolean(match()) }} />;
649
907
  ```
650
908
 
651
- ### useRouteMatches
909
+ ### useCurrentMatches
910
+
911
+ `useCurrentMatches` returns all the matches for the current matched route. Useful for getting all the route information.
912
+
913
+ For example if you stored breadcrumbs on your route definition you could retrieve them like so:
652
914
 
653
- 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:
915
+ ```js
916
+ const matches = useCurrentMatches();
654
917
 
655
- ```tsx
656
- const matches = useRouteMatches();
657
- const breadcrumbs = createMemo(() => matches().map(m => m.route.info.breadcrumb));
918
+ const breadcrumbs = createMemo(() =>
919
+ matches().map((m) => m.route.info.breadcrumb)
920
+ );
658
921
  ```
659
922
 
660
923
  ### usePreloadRoute
661
924
 
662
- Returns a function to preload a route manually — the same work link hover/focus triggers automatically. Accepts strings, URLs, and typed path nodes:
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.
663
926
 
664
- ```tsx
927
+ ```js
665
928
  const preload = usePreloadRoute();
666
- preload(paths.users(2).settings, { preloadData: true });
667
- ```
668
929
 
669
- ### useLinkState
670
-
671
- Reactive `active`/`current`/`pending` state for [custom link components](#links).
930
+ preload(`/users/settings`, { preloadData: true });
931
+ ```
672
932
 
673
933
  ### useBeforeLeave
674
934
 
675
- 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:
935
+ `useBeforeLeave` takes a function that will be called prior to leaving a route. The function will be called with:
676
936
 
677
- - `from` (_Location_): current location (before change)
678
- - `to` (_string | number_): path passed to `navigate`
679
- - `options` (_NavigateOptions_): options passed to `navigate`
680
- - `preventDefault()`: call to block the route change
681
- - `defaultPrevented` (_readonly boolean_): `true` if any previous handler called `preventDefault`
682
- - `retry(force?)`: retry the navigation, e.g. after confirming with the user; pass `true` to skip re-running leave handlers
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).
683
943
 
684
- ```tsx
944
+ Example usage:
945
+
946
+ ```js
685
947
  useBeforeLeave((e: BeforeLeaveEventArgs) => {
686
948
  if (form.isDirty && !e.defaultPrevented) {
949
+ // preventDefault to block immediately and prompt user async
687
950
  e.preventDefault();
688
951
  setTimeout(() => {
689
952
  if (window.confirm("Discard unsaved changes - are you sure?")) {
953
+ // user wants to proceed anyway so retry with force=true
690
954
  e.retry(true);
691
955
  }
692
956
  }, 100);
@@ -694,153 +958,73 @@ useBeforeLeave((e: BeforeLeaveEventArgs) => {
694
958
  });
695
959
  ```
696
960
 
697
- ## Other Environments
961
+ ## Migrations from 0.9.x
698
962
 
699
- History adapters are plain imports, so unused ones never enter your bundle:
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.
700
964
 
701
- ```tsx
702
- import { createRouter, hashHistory, memoryHistory } from "@solidjs/router";
965
+ The biggest changes are around removed APIs that need to be replaced.
703
966
 
704
- // hash mode
705
- const Router = createRouter({ routes, history: hashHistory() });
967
+ ### `<Outlet>`, `<Routes>`, `useRoutes`
706
968
 
707
- // tests and non-browser environments
708
- const Router = createRouter({ routes, history: memoryHistory("/users/1") });
709
- ```
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.
710
970
 
711
- ### Environments without Proxy
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)
712
972
 
713
- 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:
973
+ ## `element` prop removed from `Route`
714
974
 
715
- ```tsx
716
- const base = browserHistory();
717
- const history = {
718
- ...base,
719
- // wrappers build objects with defined getters instead of a Proxy
720
- utils: { ...base.utils, paramsWrapper, queryWrapper }
721
- };
722
- const Router = createRouter({ routes, history });
723
- ```
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.
724
976
 
725
- 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.
977
+ ### `data` functions & `useRouteData`
726
978
 
727
- The instance also matches arbitrary URLs anywhere — server middleware, sitemap generation, tests — with no rendering involved:
728
-
729
- ```tsx
730
- import { Router } from "./router";
731
-
732
- Router.match("/users/2/settings?tab=x");
733
- // [
734
- // { path: "/users/:id", match: "/users/2", params: { id: "2" } },
735
- // { path: "/settings", match: "/users/2/settings", params: {} }
736
- // ]
737
- ```
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.
738
980
 
739
- ## Server Integration
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:
740
982
 
741
- Framework handler wiring lives in `@solidjs/router/server`. Both integrations accept the router instance directly — its routes, base, and preload are the single source of truth:
742
-
743
- ```tsx
744
- import { createFlightDataCollector, createNoJSHandler } from "@solidjs/router/server";
745
- import { Router } from "./app/router";
746
-
747
- const collectFlightData = createFlightDataCollector(Router);
748
- const handleNoJS = createNoJSHandler();
749
- ```
750
-
751
- `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. `createNoJSHandler` implements the no-JS form convention: form posts without the client runtime redirect back with the outcome in a one-shot flash cookie that SSR reads into submission state. Both policies previously lived inside SolidStart; the router now owns them, so custom server setups get single-flight mutations and progressive enhancement without a framework.
752
-
753
- ## Migration from 0.x
754
-
755
- 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.
756
-
757
- ### Router components → `createRouter`
758
-
759
- ```tsx
760
- // 0.x
761
- <Router root={App}>
762
- <Route path="/users" component={Users} />
763
- <Route path="/users/:id" component={User} />
764
- </Router>
765
-
766
- // 1.0
767
- const Router = createRouter({
768
- routes: [
769
- { path: "/users", component: Users },
770
- { path: "/users/:id", component: User }
771
- ]
772
- });
773
-
774
- <Router>{props => <App {...props} />}</Router>
775
- ```
776
-
777
- - `<HashRouter>` → `createRouter({ routes, history: hashHistory() })`
778
- - `<MemoryRouter>` / `createMemoryHistory` → `createRouter({ routes, history: memoryHistory("/initial") })`
779
- - `<StaticRouter url>` / `<Router url>` for SSR → automatic from the request URL; without a request event, pass `memoryHistory(url)`
780
- - `root` prop → the render-prop child; `rootPreload` → the factory's `preload` option
781
-
782
- ### JSX `<Route>` → config objects
783
-
784
- 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.
785
-
786
- ### `<A>` → plain `<a>`
787
-
788
- - `<A href replace noScroll state>` → `<a href replace noscroll state>` (attributes, all lowercase)
789
- - `activeClass` / `inactiveClass` → CSS attribute selectors on `[data-active]` / `[aria-current="page"]`
790
- - `end` → style exact matches with `[aria-current="page"]` instead of `[data-active]`; the root path already only matches exactly
791
- - Route-relative hrefs → typed `paths`; `useResolvedPath` / `useHref` remain for manual resolution
792
- - Custom link components → `useLinkState`
793
-
794
- ### Removed and renamed
795
-
796
- - `<Navigate>` → call `useNavigate()` during component setup, or redirect from a preload
797
- - `useCurrentMatches` → `useRouteMatches` (same behavior)
798
- - `redirect` / `reload` → import from `@solidjs/web`; they're protocol-level and work without the router
799
- - `json(data, init)` → `respond(data, init)` from `@solidjs/web`
800
- - `cache` (deprecated alias) → `query`
801
-
802
- ### Data APIs (Solid 2)
983
+ ```js
984
+ import { lazy } from "solid-js";
985
+ import { Route } from "@solidjs/router";
803
986
 
804
- - `createAsync` / `createAsyncStore` are gone — read `query()` results with Solid 2 primitives: `createMemo`, `createProjection`, `createOptimistic`, `createOptimisticStore`.
987
+ const User = lazy(() => import("./pages/users/[id].js"));
805
988
 
806
- ```tsx
807
- // 0.x
808
- const user = createAsync(() => getUser(params.id));
989
+ // preload function
990
+ function preloadUser({ params, location }) {
991
+ const [user] = createResource(() => params.id, fetchUser);
992
+ return user;
993
+ }
809
994
 
810
- // 1.0
811
- const user = createMemo(() => getUser(params.id));
995
+ // Pass it in the route definition
996
+ <Router preload={false}>
997
+ <Route path="/users/:id" component={User} preload={preloadUser} />
998
+ </Router>;
812
999
  ```
813
1000
 
814
- - `query()` stays the source of truth for cached reads and invalidation.
815
- - `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)`.
1001
+ And then in your component taking the page props and putting them in a Context.
816
1002
 
817
- ```tsx
818
- // 0.x — read in-flight state off the submission
819
- const submitting = useSubmission(addTodo);
820
- <span>{submitting.pending && "Saving..."}</span>;
1003
+ ```js
1004
+ function User(props) {
1005
+ <UserContext.Provider value={props.data}>
1006
+ {/* my component content */}
1007
+ </UserContext.Provider>;
1008
+ }
821
1009
 
822
- // 1.0 — optimistic primitives own in-flight state
823
- const [todos, setTodos] = createOptimisticStore(() => getTodos(), []);
824
- const addTodo = action(saveTodo).onSubmit(todo =>
825
- setTodos(items => {
826
- items.push({ ...todo, pending: true });
827
- })
828
- );
1010
+ // Somewhere else
1011
+ function UserDetails() {
1012
+ const user = useContext(UserContext);
1013
+ // render stuff
1014
+ }
829
1015
  ```
830
1016
 
831
- - 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`.
832
-
833
1017
  ## SPAs in Deployed Environments
834
1018
 
835
- 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.
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.
836
1020
 
837
- On Netlify, create a `_redirects` file:
1021
+ Each provider has a different way of doing this. For example on Netlify you create a `_redirects` file that contains:
838
1022
 
839
1023
  ```sh
840
1024
  /* /index.html 200
841
1025
  ```
842
1026
 
843
- On Vercel, add a rewrites section to `vercel.json`:
1027
+ On Vercel you add a rewrites section to your `vercel.json`:
844
1028
 
845
1029
  ```json
846
1030
  {