Skip to content

Commit 4caa41c

Browse files
committed
docs: document typed URLs and navigation
1 parent 4cd1956 commit 4caa41c

6 files changed

Lines changed: 136 additions & 10 deletions

File tree

‎docs/guide/pages-routing/overview.md‎

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,42 +153,48 @@ Render and prerender flags combine like this:
153153
| Island page + `ssr = false` | Invalid. Island pages already use the island renderer. |
154154
| `output: "static"` + `prerender` unset | Auto-prerender pages that have known paths, including client-only shells. |
155155

156-
Use the `Link` component for SPA navigation between pages. It renders an `<a>` tag that intercepts clicks and navigates without a full page reload:
156+
### Typed navigation
157+
158+
Use the `Link` component for SPA navigation between pages. Build dynamic URLs with [`routeUrl()`](../type-safety.md#typed-urls-and-navigation) to check the route pattern and its parameters:
157159

158160
::: code-group
159161

160162
```tsx [React]
161163
import { Link } from "@void/react";
164+
import { routeUrl } from 'void/routes';
162165

163166
<Link href="/users">Users</Link>
164-
<Link href={`/users/${id}`}>View</Link>
167+
<Link href={routeUrl('/users/:id', { params: { id } })}>View</Link>
165168
```
166169

167170
```vue [Vue]
168171
<script setup lang="ts">
169172
import { Link } from '@void/vue';
173+
import { routeUrl } from 'void/routes';
170174
</script>
171175
172176
<template>
173177
<Link href="/users">Users</Link>
174-
<Link :href="`/users/${id}`">View</Link>
178+
<Link :href="routeUrl('/users/:id', { params: { id } })">View</Link>
175179
</template>
176180
```
177181

178182
```svelte [Svelte]
179183
<script>
180184
import { Link } from "@void/svelte";
185+
import { routeUrl } from 'void/routes';
181186
</script>
182187
183188
<Link href="/users">Users</Link>
184-
<Link href={`/users/${id}`}>View</Link>
189+
<Link href={routeUrl('/users/:id', { params: { id } })}>View</Link>
185190
```
186191

187192
```tsx [Solid]
188193
import { Link } from "@void/solid";
194+
import { routeUrl } from 'void/routes';
189195

190196
<Link href="/users">Users</Link>
191-
<Link href={`/users/${id}`}>View</Link>
197+
<Link href={routeUrl('/users/:id', { params: { id } })}>View</Link>
192198
```
193199

194200
:::
@@ -222,17 +228,28 @@ import { Link } from "@void/solid";
222228

223229
`prefetch` and `reloadDocument` work only with GET links. GET `data` becomes query parameters; use scalar values or arrays.
224230

225-
For programmatic navigation, use `useRouter()`:
231+
For programmatic navigation, use `useRouter()`. Pass a typed destination object to `visit()`, `prefetch()`, or `flush()`:
226232

227233
```ts
228234
import { useRouter } from '@void/vue'; // or "@void/react", "@void/svelte", "@void/solid"
235+
import type { RouteDestination } from 'void/routes';
229236

230237
const router = useRouter();
231-
router.visit('/users');
238+
const profile = {
239+
pathname: '/users/:id',
240+
params: { id: 42 },
241+
query: { tab: 'activity' },
242+
} satisfies RouteDestination;
243+
244+
await router.prefetch(profile);
245+
await router.visit(profile);
246+
router.flush(profile); // clear its prefetched data
232247
router.refresh(); // re-fetch current page props, keeping the same history entry
233248
router.visit('/logout', { method: 'POST' }); // non-GET navigation
234249
```
235250

251+
You can also pass URL strings or `URL` instances to these router methods. See [Typed URLs and Navigation](../type-safety.md#typed-urls-and-navigation) for query values, fragments, and catch-all routes.
252+
236253
`router.visit()` updates the page in place for same-origin URLs. `http(s)` URLs on another origin get a full page load, and other schemes such as `javascript:` are rejected.
237254

238255
For dynamic route params, use `useParams()` from the same adapter package:

‎docs/guide/type-safety.md‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,55 @@ const deleteForm = useForm('/users/:id?delete', { id: '' }, { params: { id } });
102102

103103
The `action()` helper gets the same type checking. See [Actions & Forms](./pages-routing/actions-and-forms) for the full API.
104104

105+
## Typed URLs and Navigation
106+
107+
Use `routeUrl()` from `void/routes` to build URLs from your app's page, HTTP, and WebSocket route patterns. For a page at `pages/users/[id].tsx`, the pattern is `/users/:id`:
108+
109+
```ts
110+
import { routeUrl } from 'void/routes';
111+
112+
const url = routeUrl('/users/:id', {
113+
params: { id: 42 },
114+
query: { tab: 'activity' },
115+
hash: 'details',
116+
});
117+
// /users/42?tab=activity#details
118+
```
119+
120+
Your editor autocompletes route patterns and parameter names. TypeScript checks that the route exists and all required parameters are supplied. Parameter values accept strings or numbers; Void encodes them for the URL. Query keys are chosen by your app, and values can be strings, numbers, booleans, or arrays of those values. Arrays become repeated query parameters; `null` and `undefined` values are omitted.
121+
122+
Pass the returned string to `Link`, a plain `<a>`, or `c.redirect()`. For programmatic navigation and `c.rewrite()`, you can pass a destination object directly:
123+
124+
```ts
125+
import type { RouteDestination } from 'void/routes';
126+
127+
const profile = {
128+
pathname: '/users/:id',
129+
params: { id: 42 },
130+
query: { tab: 'activity' },
131+
hash: 'details',
132+
} satisfies RouteDestination;
133+
```
134+
135+
Destination objects use the same parameter checking and URL encoding as `routeUrl()`. See [Pages Routing](./pages-routing/overview.md#typed-navigation) for links and router methods, and [Rewrites](../reference/api/rewrites.md#c-rewrite-destination) for server navigation.
136+
137+
For a catch-all page such as `pages/files/[...path].tsx`, use `/files/:path{.+}` and pass the full path as one value:
138+
139+
```ts
140+
routeUrl('/files/:path{.+}', { params: { path: 'docs/get started' } });
141+
// /files/docs/get%20started
142+
```
143+
144+
Route types update with the dev server. For CI or standalone typechecking, run `void prepare` and follow [Setup](#setup). The [API reference](../reference/api/types.md#void-routes) lists the URL helper and route types.
145+
105146
## What Gets Checked
106147

107148
| Layer | What's type-checked |
108149
| ------------ | --------------------------------------------------------------------------------------------------------------------- |
109150
| Database | Table columns, value types, insert/update shapes from Drizzle schema |
110151
| Handlers | `c.env` bindings, validator input, return type |
111152
| Fetch client | Route paths, HTTP methods, `body`/`query`/`params`, response type |
153+
| Navigation | Route patterns and required parameter names and values in `routeUrl()` and destination objects |
112154
| `useForm` | Action URLs, `data` fields, `errors` keys, `reset()` args (including `{ initial: true }`), `params`, `resetOnSuccess` |
113155
| `action()` | Action URLs, `data` payload, `params` |
114156

‎docs/reference/api.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Use this reference to find public imports, signatures, and return types. For set
2626
| [Database and seeding](./api/database.md) | Queries, operators, and seed scripts. |
2727
| [KV, storage, AI, and logging](./api/resources.md) | Resource clients, cache revalidation, and logs. |
2828
| [Deployment artifacts](./api/deploy.md) | Package built apps for managed deployment. |
29-
| [Types](./api/types.md) | Handler context, route types, and head metadata. |
29+
| [Types](./api/types.md) | Typed URLs, route types, handler context, and head metadata. |
3030
| [Environment variables](./api/env.md) | Validated environment values and binding types. |
3131
| [Pages adapters](./api/pages.md) | React, Vue, Svelte, Solid, and Markdown. |
3232

@@ -64,7 +64,7 @@ Import Void's app-facing APIs from these paths:
6464
| `void/limits` | `UsageLimit`, `UsageLimitData`, and `readUsageLimit`. AI and Sandbox limits reject with `UsageLimit`; uncaught limits in Void routes and Pages actions return structured HTTP 429 responses. |
6565
| `void/isr` | `revalidate(options)` for on-demand ISR cache invalidation |
6666
| `void/log` | `logger` structured logging helper |
67-
| `void/routes` | `RouteMap` and `WebSocketRouteMap` stubs (types only) |
67+
| `void/routes` | [`routeUrl()`](./api/types.md#routeurl-path-options), `RouteDestination`, `RoutePath`, `RouteOptions`, `RouteUrlInput`, and generated route maps |
6868
| `void/queues` | `queues` proxy for sending messages to typed queues |
6969
| `void/email` | `sendEmail`, `createEmail`, `defineEmail`, `parseEmail`, `replyEmail`, `MimeError`, `ParseEmailError`, and address, attachment, result, and handler types. See [Email](../guide/email.md). |
7070
| `void/email/testing` | `createEmailTestHarness` and `createInboundTestHarness` to capture sends and deliver test messages to `email/` handlers. |

‎docs/reference/api/pages.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ outline: deep
66

77
Framework adaptors for [Pages Routing](../../guide/pages-routing/overview). Each adaptor provides a Vite plugin and client-side runtime.
88

9+
## Navigation destinations {#navigation-destinations}
10+
11+
All four adapters support typed URLs through [`routeUrl()`](./types.md#routeurl-path-options). Pass the returned string to `Link`'s `href`.
12+
13+
The router's `visit(destination, options?)`, `prefetch(destination, options?)`, and `flush(destination, method?)` accept [`RouteUrlInput`](./types.md#route-types): a string, `URL`, or typed destination object.
14+
15+
See [Typed navigation](../../guide/pages-routing/overview.md#typed-navigation) for framework examples.
16+
917
## `@void/vue` {#void-vue}
1018

1119
### `voidVue(options?)` {#voidvue-options}

‎docs/reference/api/rewrites.md‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ URL rewrites re-dispatch a request at a different internal path without changing
1010

1111
Re-dispatches the current request at `destination` and returns the resulting `Response`. The browser URL does not change — this is a server-side hop. If `destination` has no query string, the original request query is preserved. If `destination` includes a query string, it replaces the original query. `destination` must start with `/`; known generated routes autocomplete via [`RewriteDestination`](#rewritedestination), while arbitrary strings remain accepted for dynamic paths.
1212

13+
Pass a [`RouteDestination`](./types.md#route-types) object to check the route pattern and its parameters:
14+
15+
```ts
16+
return c.rewrite({
17+
pathname: '/users/:id',
18+
params: { id: 42 },
19+
query: { tab: 'activity' },
20+
});
21+
```
22+
23+
For a typed redirect, build the URL with [`routeUrl()`](./types.md#routeurl-path-options):
24+
25+
```ts
26+
import { routeUrl } from 'void/routes';
27+
28+
return c.redirect(routeUrl('/users/:id', { params: { id: 42 } }));
29+
```
30+
1331
`c.redirect()` merges request query parameters into internal destinations, with destination values taking precedence. Redirecting a [named action](../../guide/pages-routing/actions-and-forms.md#named-actions) to a GET page drops the action key: `POST /users/42?delete&tab=profile` redirects to `/users/42?tab=profile`. Redirects that preserve the request method keep the action key: `307` and `308`, or `301` and `302` after a `PUT`, `PATCH`, or `DELETE`.
1432

1533
```ts
@@ -25,7 +43,7 @@ export const GET = defineHandler((c) => {
2543

2644
```ts
2745
interface CloudContext {
28-
rewrite(destination: RewriteDestination): Promise<Response>;
46+
rewrite(destination: RewriteDestination | RouteDestination): Promise<Response>;
2947
}
3048
```
3149

‎docs/reference/api/types.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,44 @@ Generated route types used by the typed `fetch` client. Contains each route’s
9191
```ts
9292
import type { RouteMap } from 'void/routes';
9393
```
94+
95+
## `void/routes` {#void-routes}
96+
97+
### `routeUrl(path, options?)` {#routeurl-path-options}
98+
99+
Build a relative URL string from a known page, HTTP, or WebSocket route pattern:
100+
101+
```ts
102+
import { routeUrl } from 'void/routes';
103+
104+
routeUrl('/users/:id', {
105+
params: { id: 42 },
106+
query: { tab: 'activity' },
107+
hash: 'details',
108+
});
109+
// /users/42?tab=activity#details
110+
```
111+
112+
| Option | Type | Description |
113+
| -------- | -------------- | ---------------------------------------------------------------------------------- |
114+
| `params` | Route-specific | Required for dynamic routes. Each parameter accepts a `string` or `number`. |
115+
| `query` | `RouteQuery` | Query parameters. Arrays become repeated keys; `null` and `undefined` are omitted. |
116+
| `hash` | `string` | URL fragment, such as `'details'` or `'#details'`. |
117+
118+
Static routes accept optional `query` and `hash` options. Pass raw parameter values for URL encoding. Catch-all parameters preserve `/` separators between segments. Invalid or missing path parameters throw a `TypeError`.
119+
120+
Use the returned string with `Link`, `<a>`, and `c.redirect()`. See [Typed URLs and Navigation](../../guide/type-safety.md#typed-urls-and-navigation) for setup and examples.
121+
122+
### Route types {#route-types}
123+
124+
Import these types from `void/routes`:
125+
126+
| Type | Description |
127+
| --------------------- | ----------------------------------------------------------------------------------------------- |
128+
| `RoutePathMap` | Generated map of page, HTTP, and WebSocket route patterns to their parameter names. |
129+
| `RoutePath` | Union of the known route patterns in `RoutePathMap`. |
130+
| `RouteOptions<P>` | Options for a route pattern `P`, including its required `params`, optional `query`, and `hash`. |
131+
| `RouteDestination<P>` | `{ pathname: P }` with `RouteOptions<P>`. Defaults to all known routes when `P` is omitted. |
132+
| `RouteUrlInput` | `string`, `URL`, or `RouteDestination`; accepted by router navigation methods. |
133+
| `RouteQuery` | Query keys mapped to `RouteQueryValue` or readonly arrays of those values. |
134+
| `RouteQueryValue` | `string`, `number`, `boolean`, `null`, or `undefined`. |

0 commit comments

Comments
 (0)