Skip to content

Commit 6c82d65

Browse files
docs: teach Solid Router nested routes, server rendering, setup, and route definitions
- Nested routes: rewritten around the store's account and collections sections. Explains what stays mounted and why, pathless layouts as a sign-in check, parameters flowing down, parallel preloads at every level, sharing layout data through context (Solid 2 provider syntax), and a common-problems section. - Server rendering: rewritten from observed behavior (no data request on load, one round trip per mutation, forms work with JS off) to the mechanism and the switches, using the fullstack template's server-config.ts as the reference wiring. - Route definitions: preload examples now start the query with void and read it through a memo; props.data is described as captured once rather than shown as an awaited value. - Setup: opens with who should read it and turns the option list into a when-to-reach-for-it table. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent c079fdb commit 6c82d65

4 files changed

Lines changed: 318 additions & 204 deletions

File tree

src/routes/(4)routing/(1)solid-router/(1)setup.mdx

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ version: "2.0"
44
description: "Install Solid Router, create a route tree, and mount the router instance."
55
---
66

7-
Install Solid Router in a Solid 2 application:
7+
The `basic` and `fullstack` templates from `npm create solid` already install and mount Solid Router, so if you started from one of them, skip to [Configure the router](#configure-the-router) when you need to change an option.
8+
The rest of this page is for adding the router to a project that does not have it, such as one created from the `bare` shape or an existing Vite app.
9+
10+
Install it:
811

912
```sh
1013
pnpm add @solidjs/router@next
@@ -74,22 +77,22 @@ render(() => <App />, document.getElementById("app")!);
7477

7578
## Configure the router
7679

77-
`createRouter` accepts these application-level options:
78-
79-
- `routes` is the immutable route tree used for matching and type inference.
80-
- `base` prefixes matching and generated paths.
81-
- `preload` loads root-layout data once per mount or server request.
82-
- `history` selects a history adapter.
83-
- `preloadLinks` controls route code and data preloading from link hover, focus, and touch.
84-
- `explicitLinks` limits delegated routing to anchors with a `link` attribute.
85-
- `singleFlight` controls the server-function data consumer and defaults to `true`.
86-
- `actionBase` sets the server-action URL prefix and defaults to `/_server`.
87-
- `scrollRestoration` controls explicit back and forward scroll restoration.
88-
- `transformUrl` rewrites pathnames before matching.
89-
90-
The router uses browser history by default on the client.
91-
The router uses the current request URL by default on the server.
92-
Pass `hashHistory()` or `memoryHistory()` through `history` for other client-side environments.
80+
Most apps pass only `routes`.
81+
The other options exist for a specific situation each:
82+
83+
| Option | Reach for it when |
84+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
85+
| `base` | The app is served under a prefix such as `/app`; matching and `paths` both include it. |
86+
| `history` | The app is not a normal page: an Electron shell or `file://` needs `hashHistory()`, tests need `memoryHistory()`. |
87+
| `preload` | The root layout needs data of its own; it runs once per mount or server request and reaches the root render prop as `props.data`. |
88+
| `preloadLinks` | Hover and focus preloading costs more than it saves; set `false` and use `preload="false"` per link for finer control. |
89+
| `explicitLinks` | Some anchors inside the router must stay as full page loads; only anchors with a `link` attribute are then handled by the router. |
90+
| `scrollRestoration` | The app manages scroll itself; set `false` to stop the router restoring the position on back and forward (on by default). |
91+
| `transformUrl` | Incoming pathnames need rewriting before matching, for example to strip a locale prefix. |
92+
| `singleFlight` | The router's server-function data consumer should be off; it is `true` by default. |
93+
| `actionBase` | Server actions are served from a prefix other than the default `/_server`. |
94+
95+
The router uses browser history by default on the client and the current request URL on the server, so `history` is rarely set in a web app.
9396

9497
```tsx
9598
import { createRouter, hashHistory } from "@solidjs/router";

src/routes/(4)routing/(1)solid-router/(2)route-definitions.mdx

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ version: "2.0"
44
description: "Define static, parameterized, lazy, and file-system-backed Solid Router routes."
55
---
66

7-
A route definition maps a path pattern to a component and route behavior.
7+
A route definition is a plain object: a path pattern, the component to render, and optionally a preload, children, match filters, and metadata.
8+
This page covers each field and the two places route definitions come from, a hand-written array or the file-system adapter.
89

910
```tsx
1011
import { defineRoutes } from "@solidjs/router";
@@ -16,32 +17,34 @@ export const routes = defineRoutes([
1617
]);
1718
```
1819

19-
Use `defineRoutes` for an extracted route array.
20-
The `const` type parameter preserves path literals that TypeScript would otherwise widen.
21-
`createRouter({ routes })` can then infer typed paths from the array.
22-
An inline array passed directly to `createRouter` already receives literal inference.
20+
The route array is also where the types come from.
21+
`paths.about` exists and `paths.abuot` does not because TypeScript kept the literal `"/about"`.
22+
An inline array passed to `createRouter` keeps its literals on its own; an array assigned to a variable first would widen to `string`, so wrap it in `defineRoutes` to preserve them.
2323

2424
## Type a route at its definition
2525

26-
`defineRoute` is an identity helper that types a route component and preload from the route's own path pattern.
26+
Inside a route's own `component` and `preload`, `params` is an open record by default: every key is `string | undefined`, even `:id`, which the pattern guarantees.
27+
Wrap the route in `defineRoute` and both are typed from the route's own `path`:
2728

2829
```tsx
2930
import { defineRoute } from "@solidjs/router";
3031

3132
const storyRoute = defineRoute({
3233
path: "/stories/:id/:tab?",
33-
preload: ({ params }) => getStory(params.id),
34+
preload: ({ params }) => void getStory(params.id), // params.id: string
3435
component: (props) => (
35-
<Story story={props.data} id={props.params.id} tab={props.params.tab} />
36+
<Story id={props.params.id} tab={props.params.tab} /> // tab: string | undefined
3637
),
3738
});
3839
```
3940

4041
Required parameters such as `:id` are typed as `string`.
4142
Optional parameters such as `:tab?` are typed as `string | undefined`.
42-
The return type of `preload` becomes the component's `props.data` type.
4343
Parameters inherited from a parent remain accessible as `string | undefined`.
4444

45+
Whatever `preload` returns is typed as the component's `props.data`.
46+
It is captured once when the route matches, so for async data the pattern on the [Data](/routing/solid-router/data) page is to start the query in `preload` with `void` and read it through a memo in the component, where it stays reactive to `params`.
47+
4548
For a component declared in another module, annotate it with a path witness:
4649

4750
```tsx
@@ -197,21 +200,25 @@ An optional named `route` export can supply:
197200

198201
```tsx
199202
// routes/blog/[id].tsx
203+
import { createMemo } from "solid-js";
200204
import { int, type RouteProps } from "@solidjs/router";
201205
import { defineFileRoute } from "@solidjs/router/fs";
206+
import { getPost } from "../../data/posts";
202207

203208
export const route = defineFileRoute("/blog/:id", {
204209
matchFilters: { id: int },
205-
preload: ({ params }) => getPost(params.id),
210+
preload: ({ params }) => void getPost(params.id),
206211
});
207212

208213
export default function Post(props: RouteProps<typeof route>) {
209-
return <h1>{props.data.title}</h1>;
214+
const post = createMemo(() => getPost(props.params.id));
215+
return <h1>{post().title}</h1>;
210216
}
211217
```
212218

213-
The string passed to `defineFileRoute` is a type witness for the file's path.
214-
The manifest path remains the runtime source of truth.
219+
Inside a route file the pattern lives in the filename, so there is no `paths` node to type against.
220+
The string passed to `defineFileRoute` stands in for it: it types `preload`'s params, validates `matchFilters`, and lets the config double as the component's `RouteProps` witness.
221+
The manifest path remains the runtime source of truth; if the file moves, update the string with it.
215222
When the manifest has generated literal types, route paths, filters, and search schemas continue into `Router.paths`.
216223

217224
Code-split manifest components become Solid `lazy` components.

0 commit comments

Comments
 (0)