Files
Pamela Chia 013af4ed58 feat(www): homepage json-ld, canonical, 404 links (#49533)
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 -->
2026-08-26 16:41:46 +08:00

78 lines
2.7 KiB
TypeScript

'use client'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import { Button } from 'ui'
import DefaultLayout from '@/components/Layouts/Default'
import SupabaseWordmark from '@/components/Nav/SupabaseWordmark'
const Error404 = () => {
const [show404, setShow404] = useState<boolean>(false)
useEffect(() => {
setTimeout(() => {
setShow404(true)
}, 500)
}, [])
return (
<DefaultLayout hideHeader hideFooter>
<div className="relative mx-auto flex h-screen w-full flex-col items-center justify-center">
<div className="absolute top-0 mx-auto w-full max-w-7xl px-8 pt-6 sm:px-6 lg:px-8">
<nav className="relative flex items-center justify-between sm:h-10">
<div className="flex shrink-0 grow items-center lg:grow-0">
<div className="flex w-full items-center justify-between md:w-auto">
<a href="/" className="text-foreground block w-auto h-6">
<SupabaseWordmark />
</a>
</div>
</div>
</nav>
</div>
<div className="pointer-events-none absolute" aria-hidden="true">
<div
className={`text-foreground select-none text-[14rem] opacity-5 filter transition duration-200 motion-reduce:transition-none sm:text-[18rem] lg:text-[28rem] ${
show404 ? 'blur-xs' : 'blur-none'
}`}
>
404
</div>
</div>
<div
className={`flex flex-col items-center justify-center space-y-6 transition motion-reduce:transition-none ${
show404 ? 'opacity-100' : 'opacity-0'
}`}
>
<div className="text-foreground flex w-full max-w-sm flex-col items-center justify-center space-y-3">
<h1 className="m-2 text-2xl">Looking for something? 🔍</h1>
<p className="text-center text-sm">
We couldn't find the page that you're looking for!
</p>
</div>
<div className="flex items-center space-x-4">
<Button asChild size="small">
<Link href="/">Head back</Link>
</Button>
</div>
<p className="text-foreground-lighter text-xs">
<a href="/docs" className="hover:text-foreground underline">
Documentation
</a>
{' · '}
<a href="/sitemap.xml" className="hover:text-foreground underline">
Sitemap
</a>
{' · '}
<a href="/llms.txt" className="hover:text-foreground underline">
llms.txt
</a>
</p>
</div>
</div>
</DefaultLayout>
)
}
export default Error404