diff --git a/app/ai/page.tsx b/app/ai/page.tsx index 190d948..218d540 100644 --- a/app/ai/page.tsx +++ b/app/ai/page.tsx @@ -8,6 +8,7 @@ export const metadata = getMetadata({ title: "DocumentDB for AI - Vector Search, RAG, and Embeddings", description: "Build AI applications with DocumentDB: a Mongo API-compatible document model, native vector search, PostgreSQL reliability, and self-hosted deployment.", + path: "/ai/", extraKeywords: [ "AI database", "vector search", diff --git a/app/docs/[section]/[[...slug]]/page.tsx b/app/docs/[section]/[[...slug]]/page.tsx index 3e56997..c4b4b27 100644 --- a/app/docs/[section]/[[...slug]]/page.tsx +++ b/app/docs/[section]/[[...slug]]/page.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import { notFound, redirect } from 'next/navigation'; import { capitalCase } from 'change-case'; import { getAllArticlePaths, getArticleByPath } from "../../../services/articleService"; +import { getMetadata } from "../../../services/metadataService"; import ComingSoon from "../../../components/ComingSoon"; import CommandSnippet from "../../../components/CommandSnippet"; import Markdown from "../../../components/Markdown"; @@ -48,10 +49,12 @@ export async function generateMetadata({ params }: PageProps) { const selectedNavItem = navigation.find((item) => item.link.includes(file)); const pageTitle = frontmatter.title || selectedNavItem?.title || section; - return { + return getMetadata({ title: `${pageTitle} - DocumentDB Documentation`, - description: frontmatter.description || undefined, - }; + description: frontmatter.description || '', + path: slug.length > 0 ? `/docs/${section}/${slug.join('/')}/` : `/docs/${section}/`, + type: 'article', + }); } export default async function ArticlePage({ params }: PageProps) { diff --git a/app/docs/page.tsx b/app/docs/page.tsx index eb3fb05..733f554 100644 --- a/app/docs/page.tsx +++ b/app/docs/page.tsx @@ -1,5 +1,15 @@ import Link from "next/link"; import { getArticleContent } from "../services/articleService"; +import { getMetadata } from "../services/metadataService"; + +export function generateMetadata() { + const articleContent = getArticleContent(); + return getMetadata({ + title: `${articleContent.landing.title} - DocumentDB`, + description: articleContent.landing.description, + path: '/docs/', + }); +} export default function Docs() { const articleContent = getArticleContent(); diff --git a/app/docs/reference/[type]/[category]/[name]/page.tsx b/app/docs/reference/[type]/[category]/[name]/page.tsx index a677e77..6ec0b7b 100644 --- a/app/docs/reference/[type]/[category]/[name]/page.tsx +++ b/app/docs/reference/[type]/[category]/[name]/page.tsx @@ -20,6 +20,8 @@ export async function generateMetadata({ params }: { params: Promise<{ type: str return getMetadata({ title: `${data.frontmatter.title || name} - DocumentDB MQL Reference`, description: data.frontmatter.description || '', + path: `/docs/reference/${type}/${category}/${name}/`, + type: 'article', extraKeywords: ['reference', type, category, name] }); } diff --git a/app/docs/reference/[type]/[category]/page.tsx b/app/docs/reference/[type]/[category]/page.tsx index 6ec5618..62890ad 100644 --- a/app/docs/reference/[type]/[category]/page.tsx +++ b/app/docs/reference/[type]/[category]/page.tsx @@ -17,8 +17,9 @@ export async function generateMetadata({ params }: { params: Promise<{ type: str const title = `${capitalCase(category)} ${capitalCase(pluralize(type))}`; const description = getCategoryDescription(type, category); return getMetadata({ - title: `${title} - DocumentDB MQL Reference`, + title: `${title} - DocumentDB MQL Reference`, description: await sanitizeMarkdown(description), + path: `/docs/reference/${type}/${category}/`, extraKeywords: ['reference', type, category] }); } diff --git a/app/docs/reference/[type]/page.tsx b/app/docs/reference/[type]/page.tsx index 3cb97e4..011c646 100644 --- a/app/docs/reference/[type]/page.tsx +++ b/app/docs/reference/[type]/page.tsx @@ -20,8 +20,9 @@ export async function generateMetadata({ params }: { params: Promise<{ type: str const title = capitalCase(pluralize(type)); const description = getTypeDescription(type); return getMetadata({ - title: `${title} - DocumentDB MQL Reference`, + title: `${title} - DocumentDB MQL Reference`, description: await sanitizeMarkdown(description), + path: `/docs/reference/${type}/`, extraKeywords: ['reference', type] }); } diff --git a/app/docs/reference/page.tsx b/app/docs/reference/page.tsx index 3ff1963..e95fdd1 100644 --- a/app/docs/reference/page.tsx +++ b/app/docs/reference/page.tsx @@ -12,6 +12,7 @@ export async function generateMetadata() { return getMetadata({ title: 'DocumentDB MQL Reference', description: await sanitizeMarkdown(description), + path: '/docs/reference/', extraKeywords: ['reference'] }); } diff --git a/app/kubernetes-operator/page.tsx b/app/kubernetes-operator/page.tsx index 9fc9c4b..78521e5 100644 --- a/app/kubernetes-operator/page.tsx +++ b/app/kubernetes-operator/page.tsx @@ -235,6 +235,7 @@ export const metadata = getMetadata({ title: "DocumentDB Kubernetes Operator", description: "Run DocumentDB on Kubernetes with a PostgreSQL foundation, replication, HA, backups, TLS, and hybrid or multi-cloud topologies.", + path: "/kubernetes-operator/", extraKeywords: [ "Kubernetes", "operator", diff --git a/app/packages/layout.tsx b/app/packages/layout.tsx new file mode 100644 index 0000000..0e1daad --- /dev/null +++ b/app/packages/layout.tsx @@ -0,0 +1,18 @@ +import { getMetadata } from "../services/metadataService"; + +// The packages page is a client component, so its metadata lives here. +export const metadata = getMetadata({ + title: "Download DocumentDB - Docker, APT, and RPM Packages", + description: + "Run DocumentDB locally with Docker or install the PostgreSQL extension from GPG-signed APT and RPM packages for Ubuntu, Debian, and RHEL-compatible systems.", + path: "/packages/", + extraKeywords: ["download", "install", "Docker", "APT", "RPM", "Debian", "Ubuntu", "RHEL"], +}); + +export default function PackagesLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return children; +} diff --git a/app/page.tsx b/app/page.tsx index 60f0f33..fcbb05c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,8 +2,15 @@ import Image from "next/image"; import Link from "next/link"; import CommandSnippet from "./components/CommandSnippet"; import { documentdbKubernetesOperatorDocsUrl } from "./services/externalLinks"; +import { getMetadata } from "./services/metadataService"; import { withBasePath } from "./services/sitePath"; +export const metadata = getMetadata({ + title: 'DocumentDB - Open Source Document Database', + description: 'A powerful, scalable open-source document database solution built on PostgreSQL for modern applications.', + path: '/', +}); + type CapabilityIconName = | "foundation" | "index" diff --git a/app/samples/layout.tsx b/app/samples/layout.tsx new file mode 100644 index 0000000..0191f09 --- /dev/null +++ b/app/samples/layout.tsx @@ -0,0 +1,18 @@ +import { getMetadata } from "../services/metadataService"; + +// The samples page is a client component, so its metadata lives here. +export const metadata = getMetadata({ + title: "DocumentDB Samples Gallery", + description: + "Ready-to-run DocumentDB code samples: vector search, RAG, semantic search, and full-stack applications in Python, Node.js, and TypeScript.", + path: "/samples/", + extraKeywords: ["samples", "examples", "vector search", "RAG", "Python", "Node.js", "TypeScript"], +}); + +export default function SamplesLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return children; +} diff --git a/app/services/metadataService.ts b/app/services/metadataService.ts index 5cbe99a..b8bbfc3 100644 --- a/app/services/metadataService.ts +++ b/app/services/metadataService.ts @@ -16,17 +16,32 @@ export const sanitizeMarkdown = async (markdown: string | undefined): Promise ({ +export const siteUrl = 'https://documentdb.io'; + +export const getMetadata = ({ title, description, path, type = 'website', extraKeywords = [] }: { + title: string, + description: string, + /** + * Site-relative path of the page (e.g. '/docs/reference/'), used as the + * canonical URL. Pages without a known path get no canonical tag. + */ + path?: string, + /** Open Graph object type; use 'article' for docs/reference content pages. */ + type?: 'website' | 'article', + extraKeywords?: string[], +}): Metadata => ({ + metadataBase: new URL(siteUrl), keywords: [...getBaseKeywords(), ...extraKeywords], title, description, + ...(path ? { alternates: { canonical: path } } : {}), openGraph: { - type: 'article', + type, title, description, images: [ { - url: 'https://documentdb.io/images/social-card.png', + url: `${siteUrl}/images/social-card.png`, } ] }, @@ -36,7 +51,7 @@ export const getMetadata = ({ title, description, extraKeywords = [] }: { title: description, images: [ { - url: 'https://documentdb.io/images/social-card.png', + url: `${siteUrl}/images/social-card.png`, } ] },