mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 20:05: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 -->
186 lines
6.2 KiB
TypeScript
186 lines
6.2 KiB
TypeScript
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||
import { MDXRemoteBase } from '~/features/docs/MdxBase'
|
||
import { TabPanel, Tabs } from '~/features/ui/Tabs'
|
||
import { linkTransform, UrlTransformFunction } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||
import { getGitHubFileContents, octokit, OCTOKIT_RETRY_OPTIONS } from '~/lib/octokit'
|
||
import { SerializeOptions } from '~/types/next-mdx-remote-serialize'
|
||
import { capitalize } from 'lodash-es'
|
||
import rehypeSlug from 'rehype-slug'
|
||
import { Heading } from 'ui'
|
||
import { Admonition } from 'ui-patterns'
|
||
|
||
// We fetch these docs at build time from an external repo
|
||
const org = 'supabase'
|
||
const repo = 'splinter'
|
||
const branch = 'main'
|
||
const docsDir = 'docs'
|
||
|
||
const meta = {
|
||
title: 'Performance and Security Advisors',
|
||
subtitle: 'Check your database for performance and security issues',
|
||
}
|
||
|
||
const generateMetadata = genGuideMeta(() => ({
|
||
pathname: '/guides/database/database-linter',
|
||
meta,
|
||
}))
|
||
|
||
const editLink = newEditLink('supabase/splinter/tree/main/docs')
|
||
|
||
const markdownIntro = `
|
||
You can use the Database Performance and Security Advisors to check your database for issues such as missing indexes and improperly set-up RLS policies.
|
||
|
||
## Using the Advisors
|
||
|
||
In the dashboard, navigate to [Security Advisor](https://supabase.com/dashboard/project/_/database/security-advisor) and [Performance Advisor](https://supabase.com/dashboard/project/_/database/performance-advisor) under Database. The advisors run automatically. You can also manually rerun them after you've resolved issues.
|
||
`.trim()
|
||
|
||
const getBasename = (path: string) => path.split('/').at(-1)!.replace(/\.md$/, '')
|
||
|
||
const DatabaseAdvisorDocs = async () => {
|
||
let lints: Awaited<ReturnType<typeof getLints>>['lints'] = []
|
||
let lintsList: Awaited<ReturnType<typeof getLints>>['lintsList'] = []
|
||
let fetchError: Error | null = null
|
||
|
||
try {
|
||
const data = await getLints()
|
||
lints = data.lints
|
||
lintsList = data.lintsList
|
||
} catch (error) {
|
||
fetchError = error instanceof Error ? error : new Error('Unknown error fetching advisor docs')
|
||
console.error('[database-advisors] Failed to fetch advisor docs from GitHub', fetchError)
|
||
}
|
||
|
||
const options = {
|
||
mdxOptions: {
|
||
remarkPlugins: [remarkMkDocsAdmonition, remarkPyMdownTabs, [removeTitle, meta.title]],
|
||
rehypePlugins: [[linkTransform, urlTransform(lintsList)], rehypeSlug],
|
||
},
|
||
} as SerializeOptions
|
||
|
||
return (
|
||
<GuideTemplate meta={meta} editLink={editLink} pathname="/guides/database/database-advisors">
|
||
<MDXRemoteBase source={markdownIntro} />
|
||
<Heading tag="h2">Available checks</Heading>
|
||
|
||
{fetchError ? (
|
||
<Admonition type="note" title="Couldn’t load the full Advisor library">
|
||
We fetch remediation guides straight from the <code>supabase/splinter</code> repository
|
||
during the build. GitHub timed out just now, so we’re showing the overview only.
|
||
<br />
|
||
<br />
|
||
You can check back in a few minutes or browse the
|
||
{` `}
|
||
<a
|
||
className="underline decoration-dashed underline-offset-2"
|
||
href="https://github.com/supabase/splinter/tree/main/docs"
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
>
|
||
latest Markdown on GitHub (opens in a new tab)
|
||
</a>
|
||
.
|
||
</Admonition>
|
||
) : (
|
||
<Tabs listClassNames="flex flex-wrap gap-2 [&>button]:m-0!" queryGroup="lint">
|
||
{lints.map((lint) => (
|
||
<TabPanel
|
||
key={lint.path}
|
||
id={lint.path}
|
||
label={capitalize(getBasename(lint.path).replace(/_/g, ' '))}
|
||
>
|
||
<section id={getBasename(lint.path)}>
|
||
<MDXRemoteBase source={lint.content} options={options} />
|
||
</section>
|
||
</TabPanel>
|
||
))}
|
||
</Tabs>
|
||
)}
|
||
</GuideTemplate>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* The GitHub repo uses relative links, which don't lead to the right locations
|
||
* in docs.
|
||
*
|
||
* @param url The original link, as written in the Markdown file
|
||
* @returns The rewritten link
|
||
*/
|
||
const urlTransform: (lints: Array<{ path: string }>) => UrlTransformFunction = (lints) => (url) => {
|
||
try {
|
||
const placeholderHostname = 'placeholder'
|
||
const { hostname, pathname, hash } = new URL(url, `http://${placeholderHostname}`)
|
||
|
||
// Don't modify a url with a FQDN or a url that's only a hash
|
||
if (hostname !== placeholderHostname || pathname === '/') {
|
||
return url
|
||
}
|
||
|
||
const relativePath = getBasename(pathname)
|
||
const section = lints.find(({ path }) => path === relativePath)
|
||
|
||
if (section) {
|
||
const url = new URL(window.location.href)
|
||
url.searchParams.set('lint', relativePath)
|
||
return url.toString()
|
||
}
|
||
|
||
// If we don't have this page in our docs, link to GitHub repo
|
||
return `https://github.com/${org}/${repo}/blob/${branch}${pathname}${hash}`
|
||
} catch (err) {
|
||
console.error('Error transforming markdown URL', err)
|
||
return url
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Fetch lint remediation Markdown from external repo
|
||
*/
|
||
const getLints = async () => {
|
||
const response = await octokit().request('GET /repos/{owner}/{repo}/contents/{path}', {
|
||
owner: org,
|
||
repo: repo,
|
||
path: docsDir,
|
||
ref: branch,
|
||
headers: {
|
||
'X-GitHub-Api-Version': '2022-11-28',
|
||
},
|
||
request: OCTOKIT_RETRY_OPTIONS,
|
||
})
|
||
|
||
if (response.status >= 400) {
|
||
throw new Error(
|
||
`Failed to fetch ${org}/${repo}/contents/${docsDir} docs from GitHub: ${response.status}`
|
||
)
|
||
}
|
||
|
||
if (!Array.isArray(response.data)) {
|
||
throw Error(
|
||
'Reading a directory, not a file. Should not reach this, solely to appease Typescript.'
|
||
)
|
||
}
|
||
|
||
const lintsList = response.data.filter(({ path }) => /docs\/\d+.+\.md$/.test(path))
|
||
|
||
const lints = await Promise.all(
|
||
lintsList.map(async ({ path }) => {
|
||
const content = await getGitHubFileContents({ org, repo, path, branch })
|
||
|
||
return {
|
||
path: getBasename(path),
|
||
content,
|
||
}
|
||
})
|
||
)
|
||
|
||
return { lints, lintsList }
|
||
}
|
||
|
||
export default DatabaseAdvisorDocs
|
||
export { generateMetadata }
|