diff --git a/src/components/schema/Article.tsx b/src/components/schema/Article.tsx new file mode 100644 index 0000000..2ae153b --- /dev/null +++ b/src/components/schema/Article.tsx @@ -0,0 +1,71 @@ +import type { Post, Media, Tag } from '@/payload-types' + +interface ArticleSchemaProps { + post: Post + url: string +} + +export function ArticleSchema({ post, url }: ArticleSchemaProps) { + const { title, publishDate, updatedDate, excerpt, tags = [], seo, subtitle } = post + + // Resolve tag slugs — tags may be populated objects or just IDs + const tagSlugs: string[] = (tags ?? []) + .map((t) => (typeof t === 'object' && t !== null ? (t as Tag).name ?? '' : '')) + .filter(Boolean) + + const articleSchema: Record = { + '@context': 'https://schema.org', + '@type': 'Article', + headline: seo?.title ?? title, + name: seo?.title ?? title, + description: seo?.description ?? excerpt ?? subtitle, + datePublished: publishDate, + dateModified: updatedDate ?? publishDate, + url, + mainEntityOfPage: { + '@type': 'WebPage', + '@id': url, + }, + author: { + '@type': 'Person', + name: 'Chris Arter', + url: 'https://arter.dev', + sameAs: [ + 'https://github.com/christopherarter', + 'https://bsky.app/profile/arter.dev', + 'https://www.linkedin.com/in/chrisarter1/', + ], + }, + publisher: { + '@type': 'Person', + name: 'Chris Arter', + url: 'https://arter.dev', + image: { + '@type': 'ImageObject', + url: 'https://arter.dev/images/chris-arter.jpg', + }, + }, + articleSection: tagSlugs.length > 0 ? tagSlugs[0] : 'Technology', + keywords: tagSlugs.join(', '), + } + + // Add image if available from SEO + const seoImage = seo?.image + if (seoImage && typeof seoImage === 'object') { + const img = seoImage as Media + if (img.url) { + articleSchema['image'] = { + '@type': 'ImageObject', + url: img.url.startsWith('http') ? img.url : `https://arter.dev${img.url}`, + alt: img.alt ?? title, + } + } + } + + return ( +