Metadata
Per-route document title, description, and Open Graph tags.
A page or layout can export a static metadata object, or an async
generateMetadata() for values that depend on route params:
import type { Metadata } from 'murasaki'
export const metadata: Metadata = {
title: 'Blog',
description: 'Posts about murasaki.',
}import type { GenerateMetadata } from 'murasaki'
export const generateMetadata: GenerateMetadata = async ({ params }) => {
return { title: `Post: ${params.slug}` }
}generateMetadata receives { params } — the same dynamic-segment params
useParams() reads on the client — so it can fetch a title from a CMS, an API
route, or anything else async.
The Metadata shape
interface Metadata {
title?: string
description?: string
icons?: {
icon?: string
shortcut?: string
apple?: string
}
openGraph?: {
title?: string
description?: string
images?: string[]
}
}Of icons, only icon is actually rendered to the document today (as a
<link rel="icon">) — shortcut and apple are typed for forward
compatibility but aren't applied yet. Don't rely on them showing up in the
DOM.
Layouts set defaults, pages override them
A layout's metadata (or its page's) merges root → leaf: each ancestor
layout's fields apply first, then the matched page's on top — so a layout can
set a fallback description or openGraph.images that every page under it
inherits unless it sets its own:
export const metadata: Metadata = {
title: 'My App',
openGraph: { images: ['/og-default.png'] },
}export const metadata: Metadata = {
title: 'My Post',
// openGraph.images still resolves to '/og-default.png' from the layout —
// only fields a page actually sets override the ones above it.
}The merge is per-field, not whole-object: icons and openGraph are merged
key by key (a page setting openGraph.title doesn't drop the layout's
openGraph.images), while top-level fields like title simply overwrite.
A page's generateMetadata (if present) is awaited and merged on top of that
whole static chain last — so it can override any field the static exports set,
including a layout's.
How it's applied
On every navigation, <AppRouter> resolves this whole chain and applies it to
the document:
document.title(only touched when a title actually resolves — never blanked out)<meta name="description">- Open Graph
<meta property="og:...">tags (falling back totitle/descriptionwhenopenGraphfields are omitted) <link rel="icon">, fromicons.icon
Tags Murasaki previously added are removed before the new ones are applied, so
navigating between routes replaces them cleanly instead of accumulating stale
tags from the last route. applyMetadata (the function doing this, exported
if you ever need to call it yourself) is a no-op outside a DOM environment.
This sets the document's title/meta tags, not the native window's title
bar — the window title comes from window.title in murasaki.config.ts
(see Configuration) and is set once at
launch.