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.