Route Metadata
Set titles, meta tags, and structured data per route, and serialize them into server-rendered documents.
Example
Attach title and description metadata to the route so SSG and browser navigation use the same document contract.
route('/projects/{projectId}', ProjectPage, {
meta: ({ params }) => ({
title: 'Project ' + params.projectId,
description: 'Project details and activity.',
}),
});Static metadata
`RouteMeta` covers `title`, `description`, `canonical`, `robots`, `openGraph`, `links`, `jsonLd`, and an `html` object for `lang`/`dir` — the fields that end up in `<head>` or on the document element. For a page whose metadata never changes, just pass the object directly as `RouteOptions.meta` or `PageHelperOptions.meta`; there's also a plain `title` shorthand on `RouteOptions` for the common case where only the tab title matters.
Dynamic metadata
`RouteMetaSource` is `RouteMeta | ((context) => RouteMeta | PromiseLike<RouteMeta>)`, so metadata can be computed from the resolved `RouteContext` — pulling a post title out of loader data, for instance — and it can be async if that computation needs to await something. Metadata sources stack: a route's `metaChain` collects sources from outermost group down to the specific route, and `resolveRouteMeta(record, context)` merges them in that order into the final `RouteMeta` used for the response or the client update.
Head reconciliation
After a client-side navigation, `reconcileRouteMeta(meta, target?)` updates only the `<head>` nodes Askr owns — replacing the title, meta tags, and links from the previous route with the new ones — without touching tags injected by other scripts or the initial HTML shell. **This is a client-only mechanism.** `resolveRouteMeta`/`serializeRouteMeta`/`reconcileRouteMeta` are only ever called from the router's client-side navigation code — there's no equivalent wiring in `@askrjs/askr/ssr` or `@askrjs/askr/ssg`, and `DocumentRenderContext` doesn't carry a resolved `RouteMeta` at all. Getting `RouteMeta` into your SSR/SSG document's `<head>` is on you: read it yourself (via the matched route's `meta`/`metaChain`) inside your `document: DocumentRenderer` function, or accept that the initial HTML shell's head only reflects what your document renderer put there, independent of this reconciliation mechanism.
SSG metadata
For statically generated routes, `RouteOptions.entries` returns the list of param combinations to pre-render. `resolveRouteMeta()` and `serializeRouteMeta()` are real, callable functions that will resolve a dynamic `RouteMeta` against `params.slug` and serialize it to markup — but SSG doesn't call them for you: `createStaticGen` never references either function internally, so nothing wires a route's `metaChain` into the generated HTML's `<head>` automatically. If you want per-page titles and descriptions in generated output, call `resolveRouteMeta`/`serializeRouteMeta` yourself from inside your `document: DocumentRenderer` (the same one SSR uses), passed the route and params for the entry currently being rendered.