diff --git a/app/(marketing)/blog/page.tsx b/app/(marketing)/blog/page.tsx index c3bc67b..e50f785 100644 --- a/app/(marketing)/blog/page.tsx +++ b/app/(marketing)/blog/page.tsx @@ -10,6 +10,21 @@ import { getAllPosts, getSeriesWithCounts, type BlogSeries } from '@/lib/mdx/blo export const metadata = { title: 'Blog - AI Speedrun', description: 'Artikelen over AI-gestuurde software ontwikkeling, healthcare IT en het bouwen van een EPD.', + openGraph: { + title: 'Blog - AI Speedrun', + description: 'Artikelen over AI-gestuurde software ontwikkeling, healthcare IT en het bouwen van een EPD.', + type: 'website', + siteName: 'AI Speedrun', + locale: 'nl_NL', + }, + twitter: { + card: 'summary', + title: 'Blog - AI Speedrun', + description: 'Artikelen over AI-gestuurde software ontwikkeling, healthcare IT en het bouwen van een EPD.', + }, + alternates: { + canonical: `${process.env.NEXT_PUBLIC_APP_URL || 'https://aispeedrun.vercel.app'}/blog`, + }, } export default async function BlogPage() { diff --git a/app/(marketing)/blog/serie/[serieId]/[slug]/page.tsx b/app/(marketing)/blog/serie/[serieId]/[slug]/page.tsx index 69e939e..0c9932f 100644 --- a/app/(marketing)/blog/serie/[serieId]/[slug]/page.tsx +++ b/app/(marketing)/blog/serie/[serieId]/[slug]/page.tsx @@ -31,14 +31,43 @@ export async function generateMetadata({ params }: PostPageProps) { return { title: 'Post niet gevonden' } } + const siteUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://aispeedrun.vercel.app' + const postUrl = `${siteUrl}/blog/serie/${serieId}/${slug}` + // Use post-specific image if provided, otherwise fall back to default + const ogImageUrl = post.frontmatter.image + ? `${siteUrl}${post.frontmatter.image.startsWith('/') ? '' : '/'}${post.frontmatter.image}` + : `${siteUrl}/og-blog-default.png` + return { title: `${post.frontmatter.title} - ${series.title}`, description: post.frontmatter.description, + authors: [{ name: 'Colin van der Heijden', url: 'https://ikbenlit.nl' }], + keywords: post.frontmatter.tags || [], openGraph: { title: post.frontmatter.title, description: post.frontmatter.description, type: 'article', publishedTime: post.frontmatter.date, + authors: ['Colin van der Heijden'], + siteName: 'AI Speedrun', + locale: 'nl_NL', + images: [ + { + url: ogImageUrl, + width: 1200, + height: 630, + alt: post.frontmatter.title, + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: post.frontmatter.title, + description: post.frontmatter.description, + images: [ogImageUrl], + }, + alternates: { + canonical: postUrl, }, } } @@ -55,6 +84,77 @@ export default async function PostPage({ params }: PostPageProps) { const navigation = await getSeriesNavigation(serieId, slug) const { frontmatter, content, readingTime } = post + // Generate Article structured data for SEO + const siteUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://aispeedrun.vercel.app' + const postUrl = `${siteUrl}/blog/serie/${serieId}/${slug}` + // Use post-specific image if provided, otherwise fall back to default + const articleImage = frontmatter.image + ? `${siteUrl}${frontmatter.image.startsWith('/') ? '' : '/'}${frontmatter.image}` + : `${siteUrl}/og-blog-default.png` + const articleSchema = { + '@context': 'https://schema.org', + '@type': 'Article', + headline: frontmatter.title, + description: frontmatter.description, + image: articleImage, + datePublished: frontmatter.date, + dateModified: frontmatter.date, + author: { + '@type': 'Person', + name: 'Colin van der Heijden', + url: 'https://ikbenlit.nl', + }, + publisher: { + '@type': 'Organization', + name: 'AI Speedrun', + url: siteUrl, + logo: { + '@type': 'ImageObject', + url: `${siteUrl}/images/aispeedrun-logo.webp`, + }, + }, + mainEntityOfPage: { + '@type': 'WebPage', + '@id': postUrl, + }, + articleSection: series.title, + keywords: frontmatter.tags?.join(', ') || '', + wordCount: content.split(/\s+/).length, + timeRequired: `PT${readingTime}M`, + } + + // Generate BreadcrumbList structured data + const breadcrumbSchema = { + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + itemListElement: [ + { + '@type': 'ListItem', + position: 1, + name: 'Home', + item: siteUrl, + }, + { + '@type': 'ListItem', + position: 2, + name: 'Blog', + item: `${siteUrl}/blog`, + }, + { + '@type': 'ListItem', + position: 3, + name: series.title, + item: `${siteUrl}/blog/serie/${serieId}`, + }, + { + '@type': 'ListItem', + position: 4, + name: frontmatter.title, + item: postUrl, + }, + ], + } + const colorStyles = { teal: { badge: 'bg-teal-100 text-teal-700 border-teal-200', @@ -74,6 +174,16 @@ export default async function PostPage({ params }: PostPageProps) { return (