diff --git a/docs/guide/edge/prerendering.md b/docs/guide/edge/prerendering.md index 0969a0a56..81868d5c0 100644 --- a/docs/guide/edge/prerendering.md +++ b/docs/guide/edge/prerendering.md @@ -22,14 +22,15 @@ Pages without dynamic params do not need `getPrerenderPaths()`. ## Dynamic pages (with params) -For pages with URL params, also export `getPrerenderPaths()` to specify which param combinations to prerender: +For pages with URL params, also export `getPrerenderPaths()` to specify which param combinations to prerender. Annotate the return type with [`PageParams`](../../reference/api/types.md#route-types) for that page's URL pattern, such as `/blog/:slug` for `pages/blog/[slug].server.ts`. TypeScript then requires each object to include those params, so a missing or misspelled key is a type error. Void does not infer this type from the filename. ```ts // pages/blog/[slug].server.ts +import type { PageParams } from 'void/routes'; + export const prerender = true; -export async function getPrerenderPaths() { - // Return param objects matching the URL pattern +export async function getPrerenderPaths(): Promise[]> { return [{ slug: 'hello-world' }, { slug: 'getting-started' }]; } ``` @@ -42,11 +43,13 @@ In custom SSR mode (`src/main.ssr.ts`), export `getPrerenderPaths()` at the top // src/main.ssr.ts export const prerender = true; -export async function getPrerenderPaths() { +export async function getPrerenderPaths(): Promise { return ['/', '/about', '/blog/hello-world']; } ``` +Annotate this return type as an array of path strings. Use `PageParams` on file-based pages, where the function returns param objects. + ## Relationship to revalidation Setting `routing.isr: false` in `void.config.ts` disables ISR and this edge-prerendering behavior, including per-page exports. It does not disable build-time HTML generation with `output: "static"`. diff --git a/docs/guide/ssg.md b/docs/guide/ssg.md index 025594c65..ded7332fd 100644 --- a/docs/guide/ssg.md +++ b/docs/guide/ssg.md @@ -28,11 +28,13 @@ Individual pages can opt out of prerendering: export const prerender = false; ``` -Dynamic pages with `getPrerenderPaths()` are prerendered for the returned param combinations: +Dynamic pages with `getPrerenderPaths()` are prerendered for the returned param combinations. Annotate the return type with [`PageParams`](../reference/api/types.md#route-types) so TypeScript checks the param names. Void does not infer them from the filename: ```ts // pages/blog/[slug].server.ts -export async function getPrerenderPaths() { +import type { PageParams } from 'void/routes'; + +export async function getPrerenderPaths(): Promise[]> { return [{ slug: 'hello-world' }, { slug: 'getting-started' }]; } ``` diff --git a/docs/guide/type-safety.md b/docs/guide/type-safety.md index bf2f9a917..261491c0c 100644 --- a/docs/guide/type-safety.md +++ b/docs/guide/type-safety.md @@ -157,6 +157,21 @@ The pattern must be one of your pages; HTTP and WebSocket routes are not accepte The pattern is checked by TypeScript only. Pass the pattern of the page the component renders in; without a pattern, `useParams()` returns `Record`. +### Prerender paths + +On a dynamic page, annotate `getPrerenderPaths()` with `PageParams` for that page's URL pattern. Each returned object must then include the page's params, and a missing or misspelled key is a type error. Void does not infer this type from the filename, so the check applies only when the annotation is present: + +```ts +// pages/blog/[slug].server.ts +import type { PageParams } from 'void/routes'; + +export async function getPrerenderPaths(): Promise[]> { + return [{ slug: 'hello-world' }]; +} +``` + +In custom SSR mode the function returns full path strings, so annotate it as `Promise`. See [Edge Prerendering](./edge/prerendering.md#dynamic-pages-with-params) and [Static Site Generation](./ssg.md#per-page-overrides). + ### Checked URL strings [`routing.typedRoutes`](../reference/config.md#routing-typedroutes) checks plain URL strings in `Link`'s `href`, `router.visit()`, `router.prefetch()`, `router.flush()`, and `c.rewrite()`. Apps created with `void init` have it on. To turn it on in an existing app: @@ -201,16 +216,17 @@ Route types update with the dev server. For CI or standalone typechecking, run ` ## What Gets Checked -| Layer | What's type-checked | -| ------------ | --------------------------------------------------------------------------------------------------------------------- | -| Database | Table columns, value types, insert/update shapes from Drizzle schema | -| Handlers | `c.env` bindings, validator input, return type | -| Fetch client | Route paths, HTTP methods, `body`/`query`/`params`, response type | -| Navigation | Route patterns and required parameter names and values in `routeUrl()` and destination objects | -| Links | With `routing.typedRoutes`, URL strings in `Link`, router methods, and `c.rewrite()` | -| `useParams` | Page patterns and the names of the params they return | -| `useForm` | Action URLs, `data` fields, `errors` keys, `reset()` args (including `{ initial: true }`), `params`, `resetOnSuccess` | -| `action()` | Action URLs, `data` payload, `params` | +| Layer | What's type-checked | +| ------------------- | --------------------------------------------------------------------------------------------------------------------- | +| Database | Table columns, value types, insert/update shapes from Drizzle schema | +| Handlers | `c.env` bindings, validator input, return type | +| Fetch client | Route paths, HTTP methods, `body`/`query`/`params`, response type | +| Navigation | Route patterns and required parameter names and values in `routeUrl()` and destination objects | +| Links | With `routing.typedRoutes`, URL strings in `Link`, router methods, and `c.rewrite()` | +| `useParams` | Page patterns and the names of the params they return | +| `getPrerenderPaths` | Page param names, when the return type is annotated with `PageParams` | +| `useForm` | Action URLs, `data` fields, `errors` keys, `reset()` args (including `{ initial: true }`), `params`, `resetOnSuccess` | +| `action()` | Action URLs, `data` payload, `params` | ## Serialization diff --git a/docs/reference/api/types.md b/docs/reference/api/types.md index 010045066..86cc9afb2 100644 --- a/docs/reference/api/types.md +++ b/docs/reference/api/types.md @@ -138,4 +138,6 @@ Import these types from `void/routes`: | `RouteQuery` | Query keys mapped to `RouteQueryValue` or readonly arrays of those values. | | `RouteQueryValue` | `string`, `number`, `boolean`, `null`, or `undefined`. | +Annotate a dynamic page's `getPrerenderPaths()` as `Promise[]>` so TypeScript checks those param names. See [Prerender paths](../../guide/type-safety.md#prerender-paths). + `typedRoutes` is [`routing.typedRoutes`](../config.md#routing-typedroutes). See [Checked URL strings](../../guide/type-safety.md#checked-url-strings). diff --git a/docs/reference/config.md b/docs/reference/config.md index 057276170..02c785168 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -220,7 +220,7 @@ Output mode. Controls the default rendering strategy for pages. | `"server"` (default) | `false` | `export const prerender = true` | Deploy-time (platform ISR) | | `"static"` | `true` | `export const prerender = false` | Build or deploy post-build | -When set to `"static"`, pages are prerendered to HTML in `dist/client/`. Individual pages can opt out with `export const prerender = false`. Dynamic pages need `getPrerenderPaths()` to be prerendered. +When set to `"static"`, pages are prerendered to HTML in `dist/client/`. Individual pages can opt out with `export const prerender = false`. Dynamic pages need [`getPrerenderPaths()`](../guide/ssg.md#per-page-overrides) to be prerendered. Annotate its return type with [`PageParams`](./api/types.md#route-types) so TypeScript checks the param names. When omitted or set to `"server"`, pages are server-rendered on request. Individual pages can opt into deploy-time prerendering with `export const prerender = true`, or opt out of server-rendered component HTML with `export const ssr = false`.