/** * Individual Blog Post Page * * Renders MDX content with series navigation */ import { notFound } from 'next/navigation' import Link from 'next/link' import { MDXRemote } from 'next-mdx-remote/rsc' import { getPost, getSeries, getPostsBySeries, getSeriesNavigation, getAllPosts } from '@/lib/mdx/blog' import { mdxComponents } from '../../../../documentatie/components/mdx-components' interface PostPageProps { params: Promise<{ serieId: string; slug: string }> } export async function generateStaticParams() { const posts = await getAllPosts() return posts.map((post) => ({ serieId: post.seriesId, slug: post.slug, })) } export async function generateMetadata({ params }: PostPageProps) { const { serieId, slug } = await params const post = await getPost(serieId, slug) const series = await getSeries(serieId) if (!post || !series) { 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, }, } } export default async function PostPage({ params }: PostPageProps) { const { serieId, slug } = await params const post = await getPost(serieId, slug) const series = await getSeries(serieId) if (!post || !series) { notFound() } 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', link: 'text-teal-600 hover:text-teal-700', }, amber: { badge: 'bg-amber-100 text-amber-700 border-amber-200', link: 'text-amber-600 hover:text-amber-700', }, slate: { badge: 'bg-slate-100 text-slate-700 border-slate-200', link: 'text-slate-600 hover:text-slate-700', }, } const styles = colorStyles[series.color] || colorStyles.slate return (
{/* Structured Data for SEO */}