mirror of
https://github.com/supabase/supabase.git
synced 2026-05-15 15:19:27 +08: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 -->
77 lines
2.1 KiB
TypeScript
77 lines
2.1 KiB
TypeScript
import * as NavItems from '~/components/Navigation/NavigationMenu/NavigationMenu.constants'
|
|
|
|
export interface BreadcrumbItem {
|
|
name?: string
|
|
title?: string
|
|
url?: string
|
|
}
|
|
|
|
const SECTION_PATH_TO_KEY: Record<string, keyof typeof NavItems> = {
|
|
ai: 'ai',
|
|
api: 'api',
|
|
auth: 'auth',
|
|
contributing: 'contributing',
|
|
cron: 'cron',
|
|
database: 'database',
|
|
deployment: 'deployment',
|
|
functions: 'functions',
|
|
'getting-started': 'gettingstarted',
|
|
graphql: 'graphql',
|
|
integrations: 'integrations',
|
|
'local-development': 'local_development',
|
|
platform: 'platform',
|
|
queues: 'queues',
|
|
realtime: 'realtime',
|
|
resources: 'resources',
|
|
security: 'security',
|
|
'self-hosting': 'self_hosting',
|
|
storage: 'storage',
|
|
telemetry: 'telemetry',
|
|
}
|
|
|
|
function getSectionMenu(pathname: string) {
|
|
const trimmed = pathname.replace(/^\/guides\/?/, '')
|
|
const top = trimmed.split('/')[0]
|
|
const key = SECTION_PATH_TO_KEY[top] ?? 'gettingstarted'
|
|
return (NavItems as Record<string, any>)[key]
|
|
}
|
|
|
|
function findMenuItemByUrl(
|
|
menu: any,
|
|
targetUrl: string,
|
|
parents: BreadcrumbItem[] = []
|
|
): BreadcrumbItem[] | null {
|
|
if (menu.items) {
|
|
for (const item of menu.items) {
|
|
const result = findMenuItemByUrl(item, targetUrl, [...parents, menu])
|
|
if (result) return result
|
|
}
|
|
}
|
|
if (menu.url === targetUrl) {
|
|
return [...parents, menu]
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function resolveBreadcrumbs(pathname: string): BreadcrumbItem[] {
|
|
if (pathname.startsWith('/guides/troubleshooting')) {
|
|
return [{ name: 'Troubleshooting', url: '/guides/troubleshooting' }]
|
|
}
|
|
if (pathname.startsWith('/guides/getting-started/ai-prompts')) {
|
|
return [
|
|
{ name: 'Getting started', url: '/guides/getting-started' },
|
|
{ name: 'AI Tools' },
|
|
{ name: 'Prompts', url: '/guides/getting-started/ai-prompts' },
|
|
]
|
|
}
|
|
if (pathname.startsWith('/guides/getting-started/ai-skills')) {
|
|
return [
|
|
{ name: 'Getting started', url: '/guides/getting-started' },
|
|
{ name: 'AI Tools' },
|
|
{ name: 'Agent Skills', url: '/guides/getting-started/ai-skills' },
|
|
]
|
|
}
|
|
const menu = getSectionMenu(pathname)
|
|
return findMenuItemByUrl(menu, pathname) ?? []
|
|
}
|