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/not-found.tsx b/app/not-found.tsx
new file mode 100644
index 0000000..cfe6ef2
--- /dev/null
+++ b/app/not-found.tsx
@@ -0,0 +1,68 @@
+import Link from "next/link";
+
+const suggestions = [
+ {
+ title: "Documentation",
+ description: "Guides, quick starts, and the MQL reference.",
+ href: "/docs",
+ },
+ {
+ title: "Download",
+ description: "Docker, APT, and RPM install commands.",
+ href: "/packages",
+ },
+ {
+ title: "Samples",
+ description: "Ready-to-run example applications.",
+ href: "/samples",
+ },
+] as const;
+
+export default function NotFound() {
+ return (
+
+
+ 404 - Page not found
+
+
+ This page doesn't exist
+
+
+ The page you're looking for may have moved. These are the most
+ useful places to continue from.
+
+
+
+ {suggestions.map((item) => (
+
+
+ {item.title}
+
+
{item.description}
+
+ ))}
+
+
+
+
+ );
+}
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/articleService.ts b/app/services/articleService.ts
index 4fa0a52..d8fdc59 100644
--- a/app/services/articleService.ts
+++ b/app/services/articleService.ts
@@ -5,6 +5,7 @@ import matter from 'gray-matter';
import { Article } from '../types/Article';
import { Link } from '../types/Link';
import { buildAptInstallCommand, buildRpmInstallCommand } from '../lib/packageInstall';
+import { documentdbDiscordUrl } from './externalLinks';
const articlesDirectory = path.join(process.cwd(), 'articles');
const dockerGuideContent = `# Docker Quick Start
@@ -372,7 +373,7 @@ For extension-specific help or bugs:
- [GitHub repository](https://github.com/microsoft/vscode-documentdb)
- [GitHub discussions](https://github.com/microsoft/vscode-documentdb/discussions)
- [GitHub issues](https://github.com/microsoft/vscode-documentdb/issues)
-- [DocumentDB Discord](https://discord.gg/vH7bYu524D)
+- [DocumentDB Discord](${documentdbDiscordUrl})
## Next steps
diff --git a/app/services/externalLinks.ts b/app/services/externalLinks.ts
index eab069c..bc7b4c6 100644
--- a/app/services/externalLinks.ts
+++ b/app/services/externalLinks.ts
@@ -1,3 +1,5 @@
+export const documentdbDiscordUrl = 'https://discord.gg/vH7bYu524D';
+
export const documentdbKubernetesOperatorDocsUrl =
'https://documentdb.io/documentdb-kubernetes-operator/preview/';
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`,
}
]
},
diff --git a/blogs/_config.yml b/blogs/_config.yml
index f9503d1..fe6661a 100644
--- a/blogs/_config.yml
+++ b/blogs/_config.yml
@@ -1,7 +1,11 @@
title: DocumentDB Blog
description: Insights, updates, and deep dives into the world of document databases and open-source innovation.
+# Shared external links. The Next.js app keeps the same values in
+# app/services/externalLinks.ts; update both when one changes.
+discord_url: https://discord.gg/vH7bYu524D
baseurl: /blogs
-url: ""
+# Absolute host for og:url, og:image, and the Atom feed's permanent IDs.
+url: "https://documentdb.io"
permalink: pretty
markdown: kramdown
kramdown:
diff --git a/blogs/_layouts/default.html b/blogs/_layouts/default.html
index 5d49624..f786f96 100644
--- a/blogs/_layouts/default.html
+++ b/blogs/_layouts/default.html
@@ -6,6 +6,33 @@
{% if page.title %}{{ page.title }} · {% endif %}DocumentDB
+
+ {% assign social_title = page.title | default: site.title %}
+ {% assign social_description = page.description | default: site.description %}
+ {% if page.cover_image %}
+ {% if page.cover_image contains '://' %}
+ {% assign social_image = page.cover_image %}
+ {% else %}
+ {% assign social_image = page.cover_image | absolute_url %}
+ {% endif %}
+ {% else %}
+ {% assign social_image = site.url | append: '/images/social-card.png' %}
+ {% endif %}
+
+
+
+
+
+
+ {% if page.date %}
+
+ {% endif %}
+
+
+
+
+
+
@@ -31,7 +58,7 @@