mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 04:45:08 +03:00
An agent-readiness scan of supabase.com (is-agentic.com, public report) flagged that the homepage serves no structured data and no canonical tag in raw HTML, and that the 404 page gives crawlers and agents no recovery path. I fixed both. **Changed:** - **Homepage structured data**: the raw HTML now carries Organization and WebSite JSON-LD plus `<link rel="canonical" href="https://supabase.com">`. The schema builders already existed in `lib/json-ld.ts` but were never wired to any page; this reuses the exact inline-script pattern from the blog post pages. The canonical is hardcoded to the production origin on purpose: `SITE_ORIGIN` resolves to the branch URL on previews. - **404 recovery links**: the 404 body now links to the docs, the sitemap, and llms.txt, so a dead URL leads somewhere instead of a dead end. The decorative giant "404" backdrop is a `div` instead of a second `h1`, marked `aria-hidden`, and gets `pointer-events-none`: browser testing showed the absolutely positioned backdrop was silently swallowing clicks on the new links (positioned elements paint above static siblings for hit-testing even when visually behind). - **Drift-guard test**: `md-alternates.test.ts` asserted the literal one-liner `alternates: mdAlternates('<slug>')`, which the canonical wrapper breaks. I broadened the assertion to accept the spread shape too; the rule it guards (every markdown-served slug advertises its `.md` sibling) is unchanged and still enforced. ## To test Tested locally against the dev server: - [x] `curl -s localhost:3000` and parse the two `application/ld+json` blocks: both valid JSON, types Organization and WebSite - [x] `curl -s localhost:3000 | grep canonical`: expect `<link rel="canonical" href="https://supabase.com"/>`, with the existing `text/markdown` alternate link still present - [x] `curl -s localhost:3000/some-nonexistent-page`: expect HTTP 404 with hrefs to `/docs`, `/sitemap.xml`, `/llms.txt` and exactly one `<h1>` in the body On the Vercel preview (verified via curl + Playwright browser run): - [x] View source on the preview homepage: the two JSON-LD blocks present and a canonical pointing at `https://supabase.com` (prod origin, even on the preview host) - [x] Open a nonexistent preview URL: 404 page renders the new link row under the "Head back" button, visually unchanged otherwise (screenshots in session records) - [x] Added: click each recovery link: element hit-testing returns the anchor for all three, and clicking Sitemap navigates to a valid `/sitemap.xml` document (this check caught the pointer-events regression, fixed in this PR) ## Linear - Part of GROWTH-1124 (kept open: remaining scan findings are tracked in a sub-issue) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **SEO & Discoverability** - Added canonical URL metadata and structured organization and website information to the homepage. - Improved 404 page navigation with links to Documentation, Sitemap, and `llms.txt`. - **Accessibility** - Updated the 404 page’s decorative background marker to be non-interactive and hidden from screen readers. - Added reduced-motion handling for page transitions. - **Tests** - Updated metadata validation to support multiple alternate metadata configurations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
111 lines
4.4 KiB
TypeScript
111 lines
4.4 KiB
TypeScript
import { existsSync, promises as fs } from 'node:fs'
|
|
import path from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import { SITE_ORIGIN } from './lib/constants'
|
|
import { mdAlternates } from './lib/md-alternates'
|
|
|
|
const DYNAMIC_SLUGS = ['pricing']
|
|
|
|
const MDX_SECTIONS = ['blog', 'customers', 'events']
|
|
|
|
async function collectContentMdSlugs(dir: string, prefix = ''): Promise<string[]> {
|
|
const dirents = await fs.readdir(dir, { withFileTypes: true })
|
|
const slugs: string[] = []
|
|
for (const dirent of dirents) {
|
|
const slug = prefix ? `${prefix}/${dirent.name}` : dirent.name
|
|
if (dirent.isDirectory()) {
|
|
slugs.push(...(await collectContentMdSlugs(path.join(dir, dirent.name), slug)))
|
|
} else if (dirent.name.endsWith('.md')) {
|
|
slugs.push(slug.replace(/\.md$/, ''))
|
|
}
|
|
}
|
|
return slugs
|
|
}
|
|
|
|
async function collectAppRouterPages(
|
|
dir: string,
|
|
segments: string[] = []
|
|
): Promise<Map<string, string>> {
|
|
const pages = new Map<string, string>()
|
|
const dirents = await fs.readdir(dir, { withFileTypes: true })
|
|
for (const dirent of dirents) {
|
|
if (dirent.isDirectory()) {
|
|
if (dirent.name.startsWith('[') || dirent.name.startsWith('_')) continue
|
|
const nextSegments = dirent.name.startsWith('(') ? segments : [...segments, dirent.name]
|
|
const nested = await collectAppRouterPages(path.join(dir, dirent.name), nextSegments)
|
|
nested.forEach((filePath, slug) => pages.set(slug, filePath))
|
|
} else if (dirent.name === 'page.tsx') {
|
|
pages.set(segments.join('/') || 'index', path.join(dir, dirent.name))
|
|
}
|
|
}
|
|
return pages
|
|
}
|
|
|
|
describe('mdAlternates', () => {
|
|
it('emits the absolute .md sibling URL', () => {
|
|
expect(mdAlternates('auth')).toEqual({
|
|
types: { 'text/markdown': `${SITE_ORIGIN}/auth.md` },
|
|
})
|
|
expect(mdAlternates('modules/vector')).toEqual({
|
|
types: { 'text/markdown': `${SITE_ORIGIN}/modules/vector.md` },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('markdown alternate drift', () => {
|
|
it('every markdown-served slug has a page advertising its .md sibling', async () => {
|
|
const contentSlugs = await collectContentMdSlugs(path.join(process.cwd(), 'content/md'))
|
|
const expectedSlugs = [...contentSlugs, ...DYNAMIC_SLUGS]
|
|
const appPages = await collectAppRouterPages(path.join(process.cwd(), 'app'))
|
|
|
|
expect(contentSlugs.length).toBeGreaterThan(0)
|
|
|
|
for (const slug of expectedSlugs) {
|
|
const appPagePath = appPages.get(slug)
|
|
if (appPagePath) {
|
|
const source = await fs.readFile(appPagePath, 'utf-8')
|
|
const wiringShapes = [`alternates: mdAlternates('${slug}')`, `...mdAlternates('${slug}')`]
|
|
expect(
|
|
wiringShapes.some((shape) => source.includes(shape)),
|
|
`${path.relative(process.cwd(), appPagePath)} must wire mdAlternates('${slug}') into its metadata export, either as "alternates: mdAlternates('${slug}')" or spread into a wider alternates object as "...mdAlternates('${slug}')"`
|
|
).toBe(true)
|
|
} else {
|
|
expect(
|
|
existsSync(path.join(process.cwd(), 'pages', `${slug}.tsx`)),
|
|
`no page found for markdown slug "${slug}" — App Router pages need mdAlternates, Pages Router pages are covered by _app.tsx`
|
|
).toBe(true)
|
|
}
|
|
}
|
|
})
|
|
|
|
it('pages/_app.tsx advertises the .md sibling for Pages Router pages', async () => {
|
|
const source = await fs.readFile(path.join(process.cwd(), 'pages', '_app.tsx'), 'utf-8')
|
|
expect(
|
|
source.includes('MD_PAGES.has('),
|
|
'pages/_app.tsx must gate the markdown alternate on MD_PAGES membership'
|
|
).toBe(true)
|
|
expect(
|
|
source.includes('rel="alternate" type="text/markdown"'),
|
|
'pages/_app.tsx must render the text/markdown alternate link for markdown-served slugs'
|
|
).toBe(true)
|
|
})
|
|
|
|
it.for(MDX_SECTIONS)('%s pages advertise their .md sibling', async (urlPrefix) => {
|
|
const appPagePath = path.join(process.cwd(), 'app', urlPrefix, '[slug]', 'page.tsx')
|
|
if (!existsSync(appPagePath)) {
|
|
expect(
|
|
existsSync(path.join(process.cwd(), 'pages', urlPrefix)),
|
|
`section "${urlPrefix}" has neither app/${urlPrefix}/[slug]/page.tsx nor pages/${urlPrefix}/`
|
|
).toBe(true)
|
|
return
|
|
}
|
|
const source = await fs.readFile(appPagePath, 'utf-8')
|
|
const wiring = 'alternates: mdAlternates(`' + urlPrefix + '/${slug}`)'
|
|
expect(
|
|
source.includes(wiring),
|
|
`app/${urlPrefix}/[slug]/page.tsx must contain "${wiring}" in its generateMetadata`
|
|
).toBe(true)
|
|
})
|
|
})
|