JSON-LD
Generate canonical metadata and structured data for documentation, changelogs, and API reference pages.
This functionality is already configured in the starter templates. Follow the complete setup in the Quickstart.
The generated application metadata describes the documentation to crawlers and social previews without requiring per-page hand-written tags. The templates use the page's MDX frontmatter, the navigation model, and OpenAPI metadata to emit canonical URLs, Open Graph and Twitter fields, and JSON-LD.
Set a production siteUrl first. Heyo Docs removes a trailing slash and uses
the value as the base for canonical URLs, breadcrumbs, sitemap entries, and
structured-data links.
export default heyoDocs({
title: "Acme API documentation",
description: "Reference documentation for the Acme API.",
siteUrl: "https://docs.example.com",
// ...the rest of the configuration
});siteUrl must be an HTTP(S) base URL without a query string or fragment. If it
is omitted, the pages remain renderable, but canonical and absolute structured
data URLs are not emitted.
Metadata by page type
For regular MDX documentation, Heyo Docs emits a TechArticle plus a
BreadcrumbList. The article title and description come from frontmatter;
when the description is empty, the site-level description is used.
---title: Configure webhooksdescription: Verify signed webhook deliveries from the API.---Changelog pages become CollectionPage records. Each <Update> entry is
listed as a TechArticle in hasPart, including its tags as keywords. An
OpenAPI operation becomes an APIReference with an EntryPoint that includes
the HTTP method, URL template, and request or response content type when the
schema supplies them.
All successful documentation routes declare robots: index, follow. The
framework adapters return a noindex response for a route that does not match
an MDX page or generated endpoint.
Root metadata
The root document is a WebSite. Templates also include the site title and
description, og:type=website, a summary Twitter card, a referrer policy, and
an RSS discovery link when a changelog group exists.
The JSON-LD string replaces < with \u003c before it is inserted into a
script tag. This prevents page content from ending the script element.
React Router
Create a project
The React Router template configures root metadata in app/root.tsx and
per-page metadata in app/lib/seo.ts with app/routes/docs.tsx.
bun create @heyo-sh/heyo-docs my-docs --template react-routerManual, minimal configuration
Resolve the current page from the documentation model, then return metadata
from the route's meta function. The generated helper adds title,
description, canonical, Open Graph, Twitter, JSON-LD, and breadcrumbs.
import {
changelogGroupForPage,
createDocsModel,
findDocsPage,
findOpenApiEndpoint,
} from "@heyo-sh/heyo-docs";
import type { MetaFunction } from "react-router";
import config from "../../heyo-docs.config";
import { docsSeoMeta } from "../lib/seo";
import { pages } from "virtual:heyo-docs-content";
import { openApiEndpoints } from "virtual:heyo-docs-openapi/index";
export const meta: MetaFunction = ({ params }) => {
const pathname = params["*"] ? `/${params["*"]}` : "/";
const model = createDocsModel(config, pages, [], openApiEndpoints);
const page = findDocsPage(model.pages, pathname);
const endpoint = findOpenApiEndpoint(model.endpoints, pathname);
if (!page && !endpoint)
return [
{ title: `Not found | ${config.title}` },
{ name: "robots", content: "noindex" },
];
return docsSeoMeta({
config,
pathname,
page,
endpoint,
navigation: model.navigation,
changelogGroup: page
? changelogGroupForPage(config.groups, page, model.pages)
: undefined,
});
};Use the template's app/lib/seo.ts as the helper implementation. Its output
is a React Router meta descriptor that includes the script:ld+json entry.
Next.js
Create a project
The Next.js template uses the Metadata API for standard tags and injects the
structured-data array from app/lib/seo.ts on each static docs page.
bun create @heyo-sh/heyo-docs my-docs --template nextManual, minimal configuration
Generate metadata and JSON-LD from the same resolved docs context:
import { docsContext, pathnameForSegments } from "../lib/docs";
import { docsSeo } from "../lib/seo";
export async function generateMetadata({
params,
}: {
params: Promise<{ slug?: string[] }>;
}) {
const { slug } = await params;
const pathname = pathnameForSegments(slug);
const context = docsContext(pathname);
if (!context.page && !context.endpoint)
return { robots: { index: false, follow: false } };
return docsSeo({ ...context, pathname }).metadata;
}
export default async function DocsPage({
params,
}: {
params: Promise<{ slug?: string[] }>;
}) {
const { slug } = await params;
const pathname = pathnameForSegments(slug);
const context = docsContext(pathname);
const { structuredData } = docsSeo({ ...context, pathname });
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(structuredData).replace(/</g, "\\u003c"),
}}
/>
);
}The root app/layout.tsx adds the site-wide WebSite JSON-LD and Next
metadata, including metadataBase when siteUrl is configured.
Astro
Create a project
The Astro template calls docsSeo() for every emitted page and passes its
result to DocsLayout.
bun create @heyo-sh/heyo-docs my-docs --template astroManual, minimal configuration
Resolve page data in the dynamic route and spread the metadata into a layout that renders the standard tags and JSON-LD script:
---import DocsLayout from "../layouts/docs-layout.astro";import { docsContext, pathnameForSlug } from "../lib/docs";import { docsSeo } from "../lib/seo";const pathname = pathnameForSlug(Astro.params.slug);const context = docsContext(pathname);const exists = Boolean(context.page || context.endpoint);if (!exists) Astro.response.status = 404;const seo = exists ? docsSeo({ ...context, pathname }) : { title: `Not found | ${context.config.title}`, description: context.config.description, structuredData: [], };---<DocsLayout {...seo} robots={exists ? "index, follow" : "noindex"}> <!-- documentation application --></DocsLayout>DocsLayout serialises structuredData with the same < escaping and adds
canonical, Open Graph, Twitter, RSS, and robots tags around the documentation
application.