diff --git a/web/README.md b/web/README.md
index 6bd3679..aad2a37 100644
--- a/web/README.md
+++ b/web/README.md
@@ -18,6 +18,41 @@ extracted into its own repository later without rewrites.
- `/` — landing (hero with animated REPL, features, architecture, roadmap, SDK switcher, SQL surface, desktop showcase, blog series, footer)
- `/docs` — Getting Started page (sticky sidebar nav + on-page TOC)
+## SEO surface
+
+Each public route ships full search/social metadata. The pieces:
+
+- **Per-route `
` + ``** — declared via the
+ Next App-Router `metadata` export on each `page.tsx` (and a site-wide
+ template in [`src/app/layout.tsx`](src/app/layout.tsx)).
+- **Canonical URL** — `alternates.canonical` on every page; prevents the
+ `/docs` tree (and any future hash/query variants) from being treated as
+ duplicates.
+- **OpenGraph + Twitter Card** — full set of `og:*` and `twitter:*` tags per
+ route. Heads-up: Next 15 does **not** deep-merge `openGraph` / `twitter`
+ between layout and page, so site-wide fields (`siteName`, `card`,
+ `site`/`creator`) are restated on each page-level override.
+- **Auto-generated OG images** — every page has a sibling
+ `opengraph-image.tsx` + `twitter-image.tsx` rendered via
+ `next/og`'s `ImageResponse` at the edge. Layout lives in
+ [`src/lib/og.tsx`](src/lib/og.tsx) so each route just supplies a
+ page-specific eyebrow / title / subtitle. The brand mark is inlined as
+ SVG (Satori's dynamic-font fallback 400s on uncommon glyphs).
+- **`/sitemap.xml` + `/robots.txt`** — Next 15 metadata routes
+ ([`src/app/sitemap.ts`](src/app/sitemap.ts),
+ [`src/app/robots.ts`](src/app/robots.ts)). Add a route to the `ROUTES`
+ list when shipping a new page.
+- **JSON-LD structured data** — `SoftwareApplication` schema on the landing
+ page, `BreadcrumbList` on `/docs`. Validate via Google's
+ [Rich Results Test](https://search.google.com/test/rich-results).
+- **Search Console verification** — fill in the placeholder tokens in
+ `metadata.verification` ([`src/app/layout.tsx`](src/app/layout.tsx)) once
+ Google Search Console + Bing Webmaster Tools issue them.
+
+The canonical site URL + Twitter handle live in
+[`src/lib/site.ts`](src/lib/site.ts) (`SITE.url`, `SITE.twitterHandle`) —
+update both there if the domain or handle ever changes.
+
## Local development
```sh
diff --git a/web/src/app/docs/opengraph-image.tsx b/web/src/app/docs/opengraph-image.tsx
new file mode 100644
index 0000000..8ca13a1
--- /dev/null
+++ b/web/src/app/docs/opengraph-image.tsx
@@ -0,0 +1,20 @@
+import { ImageResponse } from "next/og";
+import { OG_CONTENT_TYPE, OG_SIZE, OgFrame } from "@/lib/og";
+
+export const runtime = "edge";
+export const alt = "SQLRite docs — getting started";
+export const size = OG_SIZE;
+export const contentType = OG_CONTENT_TYPE;
+
+export default function OgImage() {
+ return new ImageResponse(
+ (
+
+ ),
+ { ...size },
+ );
+}
diff --git a/web/src/app/docs/page.tsx b/web/src/app/docs/page.tsx
index ae1be47..f0169d0 100644
--- a/web/src/app/docs/page.tsx
+++ b/web/src/app/docs/page.tsx
@@ -4,15 +4,59 @@ import { Footer } from "@/components/footer";
import { Nav } from "@/components/nav";
import { SITE } from "@/lib/site";
+const TITLE = "Getting started with SQLRite";
+const DESCRIPTION =
+ "A ten-minute tour from cargo install to a persistent on-disk SQLRite database — REPL, transactions, JOINs, vector search, BM25 full-text, and six language SDKs.";
+
export const metadata: Metadata = {
- title: "Getting started · SQLRite docs",
- description:
- "A ten-minute tour from cargo install to a persistent on-disk SQLRite database with real transactions, vector search, and full-text search.",
+ title: TITLE,
+ description: DESCRIPTION,
+ alternates: { canonical: "/docs" },
+ openGraph: {
+ type: "article",
+ siteName: "SQLRite",
+ locale: "en_US",
+ url: `${SITE.url}/docs`,
+ title: TITLE,
+ description: DESCRIPTION,
+ },
+ twitter: {
+ card: "summary_large_image",
+ site: SITE.twitterHandle,
+ creator: SITE.twitterHandle,
+ title: TITLE,
+ description: DESCRIPTION,
+ },
+};
+
+const breadcrumbJsonLd = {
+ "@context": "https://schema.org",
+ "@type": "BreadcrumbList",
+ itemListElement: [
+ {
+ "@type": "ListItem",
+ position: 1,
+ name: "Home",
+ item: SITE.url,
+ },
+ {
+ "@type": "ListItem",
+ position: 2,
+ name: "Docs",
+ item: `${SITE.url}/docs`,
+ },
+ ],
};
export default function DocsPage() {
return (
<>
+
-