mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 03:15:06 +03:00
## Summary Emits `BreadcrumbList` JSON-LD on every `/docs/guides/*` page served by `GuideTemplate`. Search engines and AI crawlers get an explicit hierarchical signal for the docs site (the marketing site already shipped JSON-LD via #45451). The chain prepends `Docs > Guides` to the existing resolver output, so a page like `/docs/guides/auth/passwords` produces a 5-level chain with the leaf URL set per Google's spec. ## Changes - New `apps/docs/lib/breadcrumbs.ts`: pure pathname → chain resolver, server-safe. Extracted from the existing client `useBreadcrumbs` hook so the same logic runs in both contexts. - New `apps/docs/lib/json-ld.ts`: `serializeJsonLd` + `breadcrumbListSchema` mirroring `apps/www/lib/json-ld.ts`. - `Breadcrumbs.tsx` (visual) now delegates to the shared resolver — single source of truth for visual + SEO chains. - `GuideTemplate` takes a required `pathname` prop and emits `<script type="application/ld+json">` next to `<Breadcrumbs />`. Skipped when the chain is empty (e.g., page not in nav menu). Middle items without URLs (e.g., the "Auth" section root) omit `item`, matching the visual breadcrumb. - 8 explicit-prop callers updated; `[[...slug]]` callers already spread `data` (which carries `pathname`). ## Scope **Out of scope:** - `/docs/reference/*` (SDK reference) — no breadcrumbs rendered today, would need separate traversal over spec JSON. - `/guides/troubleshooting/*` — uses its own template, not `GuideTemplate`. - `TechArticle` per-page schema — high maintenance for marginal value. ## Testing (Vercel preview) ```bash curl -s https://<preview>/docs/guides/auth/passwords | grep -oE '<script type="application/ld\+json"[^>]*>[^<]+</script>' ``` Expect a script tag with the chain `Docs > Guides > Auth > Flows (How-tos) > Password-based`, leaf URL `https://supabase.com/docs/guides/auth/passwords`. - [x] `/docs/guides/auth/passwords` — 5-item chain, leaf URL present - [x] `/docs/guides/getting-started/features` — 4-item chain, all items have URLs - [x] `/docs/guides/getting-started/ai-prompts/<slug>` — special-case chain (`Getting started > AI Tools > Prompts > <slug>`), leaf URL falls back to pathname - [x] `/docs/guides/database/database-advisors` (explicit-prop caller) — chain renders - [x] Visual breadcrumb on the same pages still renders correctly - [ ] Validate output through [Google Rich Results Test](https://search.google.com/test/rich-results) on a deployed preview URL - [x] `/docs/guides/troubleshooting/<slug>` — no JSON-LD emitted (different template, intentional) ## Linear - fixes GROWTH-820 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added JSON-LD breadcrumb markup to guide pages to improve search/discovery. * **Improvements** * Centralized breadcrumb generation for consistent, accurate breadcrumbs across guides. * Multiple guide pages updated to ensure breadcrumbs and page context display correctly. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
46 lines
1.3 KiB
TypeScript
46 lines
1.3 KiB
TypeScript
import type { BreadcrumbItem } from '~/lib/breadcrumbs'
|
|
import { PROD_URL } from '~/lib/constants'
|
|
|
|
type JsonLdSchema = Record<string, unknown> | Record<string, unknown>[]
|
|
|
|
export function serializeJsonLd(schema: JsonLdSchema): string {
|
|
return JSON.stringify(schema)
|
|
.replace(/</g, '\\u003c')
|
|
.replace(/>/g, '\\u003e')
|
|
.replace(/&/g, '\\u0026')
|
|
}
|
|
|
|
const DOCS_ROOT: BreadcrumbItem = { name: 'Docs', url: '' }
|
|
const GUIDES_ROOT: BreadcrumbItem = { name: 'Guides', url: '/guides' }
|
|
|
|
interface BreadcrumbListSchemaInput {
|
|
pathname: string
|
|
chain: BreadcrumbItem[]
|
|
}
|
|
|
|
export function breadcrumbListSchema({ pathname, chain }: BreadcrumbListSchemaInput) {
|
|
const fullChain: BreadcrumbItem[] = [DOCS_ROOT, GUIDES_ROOT, ...chain]
|
|
|
|
const itemListElement = fullChain.map((crumb, index) => {
|
|
const isLeaf = index === fullChain.length - 1
|
|
const name = crumb.title ?? crumb.name
|
|
const path = isLeaf ? pathname : crumb.url
|
|
|
|
const listItem: Record<string, unknown> = {
|
|
'@type': 'ListItem',
|
|
position: index + 1,
|
|
name,
|
|
}
|
|
if (path !== undefined) {
|
|
listItem.item = path === '' ? PROD_URL : `${PROD_URL}${path}`
|
|
}
|
|
return listItem
|
|
})
|
|
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BreadcrumbList',
|
|
itemListElement,
|
|
}
|
|
}
|