mirror of
https://github.com/supabase/supabase.git
synced 2026-05-06 22:18:00 +08:00
## Summary
- Adds `breadcrumbListSchema(items)` helper to `apps/www/lib/json-ld.ts`
and a hand-curated `apps/www/lib/breadcrumbs.ts` route map.
- Wires inline `<script type="application/ld+json">` BreadcrumbList
blocks into 18 marketing surfaces: blog (index + slug), customers (index
+ slug), events (index + slug), 5 product pages (database, auth,
storage, edge-functions, realtime), 3 modules (vector, cron, queues),
pricing, careers, company, features.
- Pages router callers wrap the script in `<Head>`; app router callers
place it directly in JSX. Dynamic surfaces append a leaf at render time
using the page's title (`frontmatter.title` for blog, `meta_title ??
title` for customers, `event.meta_title ?? event.title` for events).
- Modules sit at `Home > {Name}` since no `/modules` index page exists;
products sit at `Home > {Product}` (no shared products parent). Absolute
`https://supabase.com` URLs match the existing `CANONICAL_ORIGIN`
convention so anchors stay stable across Vercel previews.
Linear:
[GROWTH-822](https://linear.app/supabase/issue/GROWTH-822/add-breadcrumblist-json-ld-to-www-marketing-surfaces)
(sub-issue under
[GROWTH-724](https://linear.app/supabase/issue/GROWTH-724)).
> **Note on branch name:** the branch is
`pamela/growth-820-www-breadcrumb-jsonld`; the actual Linear issue is
GROWTH-822. The branch was named before the sub-issue was created.
Ignore the `820` in the branch.
Explicitly deferred (separate PRs / low SEO ROI): `/launch-week/*`,
`/solutions/*`, `/partners/*`, `/alternatives/*`, `/changelog`,
`/legal/dpa`, `/aws-reinvent-2025`, `/wrapped`, `/contribute/*`,
`/brand-assets`, `/ga`, `/ga-week`, `/state-of-startups*`, and the
homepage (Organization + WebSite already cover homepage entity signals;
single-item BreadcrumbList is ignored by Google).
## Test plan
- [x] On the Vercel preview, `curl -s https://<preview>/database | grep
'"BreadcrumbList"'` returns the script block with `Home > Database`.
- [x] `curl -s https://<preview>/blog/<recent-slug> | grep
'"BreadcrumbList"'` returns `Home > Blog > {post title}`.
- [x] `curl -s https://<preview>/customers/<slug> | grep
'"BreadcrumbList"'` returns `Home > Customer Stories > {customer
title}`.
- [x] `curl -s https://<preview>/events/<slug> | grep
'"BreadcrumbList"'` returns `Home > Events > {event title}`.
- [x] `curl -s https://<preview>/modules/vector | grep
'"BreadcrumbList"'` returns `Home > Vector`.
68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
import FeaturedThumb from 'components/Blog/FeaturedThumb'
|
|
import DefaultLayout from 'components/Layouts/Default'
|
|
import type { Metadata } from 'next'
|
|
import type PostTypes from 'types/post'
|
|
|
|
import BlogClient from './BlogClient'
|
|
import { breadcrumbs } from '@/lib/breadcrumbs'
|
|
import { breadcrumbListSchema, serializeJsonLd } from '@/lib/json-ld'
|
|
import { getSortedPosts } from '@/lib/posts'
|
|
|
|
export const revalidate = 30
|
|
|
|
export const metadata: Metadata = {
|
|
title: 'Supabase Blog: the Postgres development platform',
|
|
description: 'Get all your Supabase News on the Supabase blog.',
|
|
openGraph: {
|
|
title: 'Supabase Blog: the Postgres development platform',
|
|
description: 'Get all your Supabase News on the Supabase blog.',
|
|
url: 'https://supabase.com/blog',
|
|
images: [{ url: 'https://supabase.com/images/og/supabase-og.png' }],
|
|
},
|
|
}
|
|
|
|
const INITIAL_POSTS_LIMIT = 25
|
|
|
|
export default async function BlogPage() {
|
|
const staticPostsData = getSortedPosts({ directory: '_blog', runner: '** BLOG PAGE **' })
|
|
|
|
const allPosts = [...staticPostsData].sort((a, b) => {
|
|
const dateA = new Date(a.date || a.formattedDate).getTime()
|
|
const dateB = new Date(b.date || b.formattedDate).getTime()
|
|
return dateB - dateA
|
|
})
|
|
|
|
const initialPosts = allPosts.slice(0, INITIAL_POSTS_LIMIT)
|
|
const featuredPost = initialPosts[0]
|
|
// Featured post is rendered as the hero above the list, so exclude it from
|
|
// the list to avoid showing it twice. BlogClient compensates the API offset
|
|
// when scrolling for more posts.
|
|
const listPosts = initialPosts.slice(1)
|
|
const totalListPosts = Math.max(0, allPosts.length - 1)
|
|
|
|
return (
|
|
<>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{
|
|
__html: serializeJsonLd(breadcrumbListSchema(breadcrumbs.blogIndex)),
|
|
}}
|
|
/>
|
|
<DefaultLayout>
|
|
<h1 className="sr-only">Supabase blog</h1>
|
|
<div className="container relative mx-auto px-4 py-4 md:py-8 xl:py-10 sm:px-16 xl:px-20">
|
|
{featuredPost && (
|
|
<FeaturedThumb key={featuredPost.slug} {...(featuredPost as PostTypes)} />
|
|
)}
|
|
</div>
|
|
|
|
<div className="border-default border-t">
|
|
<div className="container mx-auto px-4 py-4 md:py-8 xl:py-10 sm:px-16 xl:px-20">
|
|
<BlogClient initialBlogs={listPosts} totalPosts={totalListPosts} />
|
|
</div>
|
|
</div>
|
|
</DefaultLayout>
|
|
</>
|
|
)
|
|
}
|