SEO and document head
Compose escaped route metadata across layouts and pages, understand managed SEO precedence, and update the client document safely.
Choose metadata ownership
Use a route head export for metadata owned by one page or layout.
Return escaped TSX, including
Seo, when possible.Use
Seofor title, description, canonical URL, robots, keywords, author, theme color, Open Graph, and Twitter fields.Use the
Headcomponent for metadata owned by the mounted lifetime of a reusable component outside route head composition.Use
unsafeHeadHtmlonly for a raw-string boundary that cannot be expressed as TSX. It is not escaped and must never contain user-controlled input.
Return escaped TSX from a route
A head function may read route params, loader data, the URL, headers, signal, layout data, and the route error.
A title element is normalized into the document title; other escaped nodes become head contributions.
PageHeadobjects may additionally set status,htmlAttributes,bodyAttributes, andunsafeHeadHtml.CSR-only routes cannot depend on a dynamic server head result; route inspection reports invalid combinations.
tsximport { Seo } from "@tavojs/core";
import type {
PageLoadContext,
PageProps,
} from "@tavojs/core/router";
type Project = {
id: string;
name: string;
summary: string;
};
export async function load({
params,
}: PageLoadContext): Promise<Project> {
return {
id: params.id ?? "",
name: `Project ${params.id}`,
summary: "A Tavo.js project.",
};
}
export function head({
data,
}: {
data: Project;
}) {
return (
<Seo
title={data.name}
description={data.summary}
canonical={`https://example.com/projects/${data.id}`}
openGraph={{
type: "website",
image: "https://example.com/project-card.png",
}}
twitter={{ card: "summary_large_image" }}
/>
);
}
export default function ProjectPage({
data,
}: PageProps<Project>) {
return <main><h1>{data?.name}</h1></main>;
}Predict layout and page precedence
Head layers merge from the root layout toward the page. Later scalar and attribute values override earlier defaults. Escaped nodes retain contribution order, while Seo fields use stable managed identities.
The page title and status override layout values when present.
htmlAttributesandbodyAttributesmerge by attribute name, with the later layer winning.A later
Seovalue replaces an earlier managed value for the same standard meta, Open Graph property, Twitter field, or canonical link.An unkeyed raw TSX node remains an ordered contribution;
Tavo.jsdoes not silently deduplicate arbitrary duplicate meta tags.Multiple
Seodeclarations retain their position relative to surrounding raw nodes while their defined fields merge.An explicit nested Open Graph or Twitter field survives a later top-level fallback that does not replace that nested field.
tsximport { Seo } from "@tavojs/core";
import type { PropsWithChildren } from "@tavojs/core";
export function head() {
return (
<Seo
title="Acme"
description="Acme project workspace."
openGraph={{ siteName: "Acme" }}
/>
);
}
export default function RootLayout({
children,
}: PropsWithChildren) {
return <div>{children}</div>;
}tsximport { Seo } from "@tavojs/core";
export function head() {
return [
<Seo
title="About Acme"
description="How Acme builds project software."
/>,
<meta
name="release-channel"
content="stable"
/>,
];
}
export default function AboutPage() {
return <main><h1>About Acme</h1></main>;
}Understand Seo field fallbacks
An explicit robots string takes precedence over
noIndexandnoFollow.Keyword arrays become one comma-separated meta value.
Open Graph title and description fall back to top-level title and description; Open Graph URL falls back to canonical.
Twitter title and description fall back to top-level values; Twitter image falls back to the Open Graph image.
Empty optional values do not emit metadata.
Keep raw HTML visibly unsafe
tsxexport function head() {
return {
title: "Trusted vendor integration",
unsafeHeadHtml:
'<meta name="partner-widget" content="enabled">',
};
}Verify SSR and navigation output
Render the route through SSR and assert exactly one title and one expected managed tag per
Seofield.Compose a layout default with a page override and confirm the page wins without losing unrelated nested metadata.
Mix
Seoand raw TSX nodes and verify their intended order.Navigate between routes and confirm stale description, canonical, robots, Open Graph, and Twitter fields are removed.
Navigate to a route without a title and confirm the configured document title returns.
Inspect the application root and confirm title, meta, and canonical nodes were hoisted into head rather than left in page content.
Look up exact public types
Follow linked API names to their canonical TypeScript declarations and package boundaries.