From e2480538adbca7bec14da98f0bc9ffd3e9a95a64 Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Fri, 1 May 2026 23:55:32 +0900 Subject: [PATCH] feat(www): add JSON-LD structured data to homepage, products, and blog (#45451) --- apps/www/app/blog/[slug]/page.tsx | 42 +++++++++- apps/www/lib/json-ld.ts | 122 ++++++++++++++++++++++++++++++ apps/www/pages/auth.tsx | 17 +++++ apps/www/pages/database.tsx | 17 +++++ apps/www/pages/edge-functions.tsx | 38 +++++++--- apps/www/pages/index.tsx | 7 ++ apps/www/pages/modules/vector.tsx | 27 +++++-- apps/www/pages/realtime.tsx | 40 +++++++--- apps/www/pages/storage.tsx | 17 +++++ 9 files changed, 297 insertions(+), 30 deletions(-) create mode 100644 apps/www/lib/json-ld.ts diff --git a/apps/www/app/blog/[slug]/page.tsx b/apps/www/app/blog/[slug]/page.tsx index 6e6fc8fb88c..4af56dd149f 100644 --- a/apps/www/app/blog/[slug]/page.tsx +++ b/apps/www/app/blog/[slug]/page.tsx @@ -2,11 +2,27 @@ import type { Metadata } from 'next' import { draftMode } from 'next/headers' import BlogPostClient from './BlogPostClient' -import { getAbsoluteBlogSocialImage } from '@/lib/blog-images' +import authors from '@/lib/authors.json' +import { + BLOG_PLACEHOLDER_IMAGE, + getAbsoluteBlogSocialImage, + toAbsoluteBlogImageUrl, +} from '@/lib/blog-images' import { SITE_ORIGIN } from '@/lib/constants' +import { blogPostingSchema, serializeJsonLd } from '@/lib/json-ld' import { getAllPostSlugs, getPostdata, getSortedPosts } from '@/lib/posts' import type { Blog, BlogData, PostReturnType } from '@/types/post' +function resolveBlogAuthors(authorField: string | undefined) { + return (authorField ?? '') + .split(',') + .map((id) => id.trim()) + .filter(Boolean) + .map((id) => authors.find((a) => a.author_id === id)) + .filter((a): a is (typeof authors)[number] => Boolean(a)) + .map((a) => ({ name: a.author, url: a.author_url })) +} + export const revalidate = 30 type MatterReturn = { @@ -138,6 +154,28 @@ export default async function BlogPostPage({ params }: { params: Promise isDraftMode: isDraft, } - return + const frontmatter = parsedContent.data + const blogAuthors = resolveBlogAuthors(frontmatter.author) + const imageUrl = + getAbsoluteBlogSocialImage(frontmatter, SITE_ORIGIN) ?? + toAbsoluteBlogImageUrl(BLOG_PLACEHOLDER_IMAGE, SITE_ORIGIN) + const blogJsonLd = blogPostingSchema({ + url: `${SITE_ORIGIN}/blog/${slug}`, + headline: frontmatter.title, + description: frontmatter.description, + image: imageUrl, + datePublished: frontmatter.date, + authors: blogAuthors.length > 0 ? blogAuthors : [{ name: 'Supabase' }], + }) + + return ( + <> +