Files
Ben Sabic 927d0dbd7d docs: add cross-link card sections and page-level SEO metadata (#804)
Many docs pages are orphaned: nothing links to them apart from the
sidebar, so readers and crawlers rarely find them. This PR gives every
docs page a Read more section with four cards at the bottom of the
article, above the prev/next footer.

Cards are picked deterministically in lib/read-more.ts: the page's
related frontmatter first, then prerequisites, then siblings from the
same sidebar section, then the rest of the page tree, so every page
always fills all four slots. Card titles and descriptions come from the
target page's own frontmatter, nothing is duplicated. The section is
injected through the MDX wrapper slot in the docs route, so it applies
to all pages without touching content.

To make the links topical rather than positional, 26 pages get related
frontmatter additions. The 20 pages that no other page referenced (all
ten api/ pages among them) now each have at least one inbound link,
generally pairing guides with their API reference and back. The bundled
copy of create-chat-sdk.mdx is synced to keep the byte-match test green.

Official adapter pages get the same treatment with a More adapters
section: same-type adapters first (platform or state, using the catalog
order), topped up from the other official group. Vendor-official and
community adapters are never shown, and their pages don't render the
section. It reuses AdapterCard, so logos and package names match the
listing page.

Two small SEO fixes ride along. JSON-LD was allowlisted to three docs
pages; the allowlist is gone, so all 45 now emit HowTo or TechArticle
plus a BreadcrumbList. Docs and adapter detail pages also emit canonical
URLs now, resolved against the existing metadataBase.

Verified against the production build: all 45 docs pages and all 19
official adapter pages render exactly four cards, no page is left
unreferenced, canonicals and JSON-LD are present everywhere, and pnpm
validate passes. Docs-only, so no changeset.

---------

Signed-off-by: Ben Sabic <bensabic@users.noreply.github.com>
Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-11 08:12:05 +10:00

111 lines
2.7 KiB
TypeScript

import type { TableOfContents } from "fumadocs-core/toc";
const BASE_URL = "https://chat-sdk.dev";
const VERCEL_ORG = {
"@type": "Organization",
name: "Vercel",
url: "https://vercel.com",
};
interface DocsPage {
data: {
description?: string;
title?: string;
toc?: TableOfContents;
type?: string;
};
url: string;
}
const getDocsPageUrl = (pageUrl: string) =>
pageUrl.startsWith("http") ? pageUrl : `${BASE_URL}${pageUrl}`;
const getDocsBreadcrumb = (title: string, pageUrl: string) => ({
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "Chat SDK", item: BASE_URL },
{
"@type": "ListItem",
position: 2,
name: "Documentation",
item: `${BASE_URL}/docs`,
},
{ "@type": "ListItem", position: 3, name: title, item: pageUrl },
],
});
const getStepNameFromTocEntry = (entry: {
title: TableOfContents[number]["title"];
url: string;
}): string => {
if (typeof entry.title === "string" && entry.title.length > 0) {
return entry.title;
}
const hash = entry.url.startsWith("#") ? entry.url.slice(1) : entry.url;
return hash
.split("-")
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
.join(" ");
};
const getHowToJsonLd = (
title: string,
description: string | undefined,
pageUrl: string,
toc: DocsPage["data"]["toc"]
) => {
const steps = (toc ?? [])
.filter((entry) => entry.depth === 2)
.map((entry, index) => ({
"@type": "HowToStep",
position: index + 1,
name: getStepNameFromTocEntry(entry),
url: `${pageUrl}${entry.url}`,
}));
return {
"@context": "https://schema.org",
"@type": "HowTo",
name: title,
description,
url: pageUrl,
publisher: VERCEL_ORG,
...(steps.length > 0 ? { step: steps } : {}),
};
};
const getTechArticleJsonLd = (
title: string,
description: string | undefined,
pageUrl: string
) => ({
"@context": "https://schema.org",
"@type": "TechArticle",
headline: title,
description,
url: pageUrl,
author: VERCEL_ORG,
publisher: VERCEL_ORG,
});
/**
* Build JSON-LD for documentation pages.
* Guides (`type: guide`) emit `HowTo` with h2 sections as steps; other pages
* use `TechArticle`. Always includes a `BreadcrumbList`.
*/
export const getDocsJsonLd = (page: DocsPage) => {
const pageUrl = getDocsPageUrl(page.url);
const { description, type, toc } = page.data;
const title = page.data.title ?? "Chat SDK";
const breadcrumb = getDocsBreadcrumb(title, pageUrl);
if (type === "guide") {
return [getHowToJsonLd(title, description, pageUrl, toc), breadcrumb];
}
return [getTechArticleJsonLd(title, description, pageUrl), breadcrumb];
};