Navigated to /docs/core/seo-and-head

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 Seo for title, description, canonical URL, robots, keywords, author, theme color, Open Graph, and Twitter fields.

  • Use the Head component for metadata owned by the mounted lifetime of a reusable component outside route head composition.

  • Use unsafeHeadHtml only 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.

  • PageHead objects may additionally set status, htmlAttributes, bodyAttributes, and unsafeHeadHtml.

  • CSR-only routes cannot depend on a dynamic server head result; route inspection reports invalid combinations.

TSX
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.

  • htmlAttributes and bodyAttributes merge by attribute name, with the later layer winning.

  • A later Seo value 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.js does not silently deduplicate arbitrary duplicate meta tags.

  • Multiple Seo declarations 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.

TSX
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>;
}
TSX
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>;
}

Replace managed metadata during navigation

  • Client navigation removes stale route-managed metadata before applying the resolved route head.

  • When the next route contributes no title, Tavo.js restores the document title configured before route metadata was applied.

  • Seo renders the same managed fields in SSR and client navigation, preventing standard metadata from accumulating across routes.

  • Component Head inserts its children for the mounted lifetime and removes those nodes during cleanup. A supplied title is restored when that Head owner is disposed.

Understand Seo field fallbacks

  • An explicit robots string takes precedence over noIndex and noFollow.

  • 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

TSX
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 Seo field.

  • Compose a layout default with a page override and confirm the page wins without losing unrelated nested metadata.

  • Mix Seo and 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.