mirror of
https://github.com/supabase/supabase.git
synced 2026-05-16 15:49:19 +08:00
## Summary Eliminates the Google Search Console "Missing field 'item' (in 'itemListElement')" critical error on 230 `/docs/guides/*` pages. The schema was emitting `ListItem`s without an `item` field for intermediate category nodes that lack a URL in the docs nav. Per [Google's spec](https://developers.google.com/search/docs/appearance/structured-data/breadcrumb), `item` is required on every BreadcrumbList position except the last leaf — so url-less items are filtered out instead. Also fixes a smaller quality gap surfaced during preview verification: the `auth` section root in `NavigationMenu.constants.ts` was missing a `url`, so auth trails were dropping the "Auth" breadcrumb level (`Docs > Guides > JSON Web Tokens (JWT) > Overview` instead of `Docs > Guides > Auth > JSON Web Tokens (JWT) > Overview`). Every other section root already has a `url`; auth was the lone outlier. ## Testing Tested locally via vitest (`pnpm --filter docs exec vitest run lib/json-ld.test.ts`): - [x] All-urls chain: every `itemListElement` has string `item` and `name` - [x] Leaf-url-mismatch: leaf uses `pathname` even when the chain leaf URL differs - [x] All-url-less chain: returns `null` - [x] Empty chain: returns `null` Tested on the preview deploy against 7 representative GSC-flagged paths: - [x] `/docs/guides/getting-started/ai-prompts` — 4 positions, 0 missing - [x] `/docs/guides/getting-started/ai-skills` — 4 positions, 0 missing - [x] `/docs/guides/auth/jwts` — 4 positions, 0 missing (after auth fix: includes "Auth") - [x] `/docs/guides/auth/social-login/auth-google` — 4 positions, 0 missing (after auth fix: includes "Auth") - [x] `/docs/guides/database/postgres-js` — 4 positions, 0 missing - [x] `/docs/guides/storage/quickstart` — 4 positions, 0 missing - [x] `/docs/guides/platform/migrating-within-supabase/dashboard-restore` — 5 positions, 0 missing Post-merge: - [ ] validator.schema.org against deployed URL: 0 errors - [ ] GSC "Validate fix" on the breadcrumb issue (1-2 week re-crawl window) ## Linear - fixes GROWTH-835 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved breadcrumb validation to filter incomplete entries and avoid broken documentation links. * Restored root link for the Auth navigation section so the Auth menu item now navigates to /guides/auth. * **Tests** * Added comprehensive tests covering breadcrumb generation and edge cases. * **Refactor** * Streamlined breadcrumb JSON‑LD schema generation for clearer output and maintainability. [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45744) <!-- end of auto-generated comment: release notes by coderabbit.ai -->
69 lines
1.9 KiB
TypeScript
69 lines
1.9 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')
|
|
}
|
|
|
|
type ValidCrumb = BreadcrumbItem & { url: string }
|
|
|
|
const DOCS_ROOT: ValidCrumb = { name: 'Docs', url: '' }
|
|
const GUIDES_ROOT: ValidCrumb = { name: 'Guides', url: '/guides' }
|
|
|
|
interface BreadcrumbListSchemaInput {
|
|
pathname: string
|
|
chain: BreadcrumbItem[]
|
|
}
|
|
|
|
const warnedPaths = new Set<string>()
|
|
|
|
function isValidCrumb(crumb: BreadcrumbItem): crumb is ValidCrumb {
|
|
return crumb.url !== undefined && Boolean(crumb.title ?? crumb.name)
|
|
}
|
|
|
|
export function breadcrumbListSchema({ pathname, chain }: BreadcrumbListSchemaInput) {
|
|
const filteredChain = chain.filter(isValidCrumb)
|
|
|
|
if (
|
|
process.env.NODE_ENV !== 'production' &&
|
|
filteredChain.length !== chain.length &&
|
|
!warnedPaths.has(pathname)
|
|
) {
|
|
warnedPaths.add(pathname)
|
|
const dropped = chain
|
|
.filter((crumb) => !isValidCrumb(crumb))
|
|
.map((crumb) => crumb.title ?? crumb.name ?? '<unnamed>')
|
|
console.warn(
|
|
`[json-ld] Dropping breadcrumb items missing url or name from ${pathname}: ${dropped.join(', ')}`
|
|
)
|
|
}
|
|
|
|
if (filteredChain.length === 0) return null
|
|
|
|
const fullChain: ValidCrumb[] = [DOCS_ROOT, GUIDES_ROOT, ...filteredChain]
|
|
|
|
const itemListElement = fullChain.map((crumb, index) => {
|
|
const isLeaf = index === fullChain.length - 1
|
|
const path = isLeaf ? pathname : crumb.url
|
|
const itemUrl = path === '' ? PROD_URL : `${PROD_URL}${path}`
|
|
|
|
return {
|
|
'@type': 'ListItem',
|
|
position: index + 1,
|
|
name: crumb.title ?? crumb.name,
|
|
item: itemUrl,
|
|
}
|
|
})
|
|
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BreadcrumbList',
|
|
itemListElement,
|
|
}
|
|
}
|