From 7df92dd2fb585857ea128b7e196c76f41cc903be Mon Sep 17 00:00:00 2001 From: colinislit Date: Thu, 1 Jan 2026 10:56:34 +0100 Subject: [PATCH] feat(blog): enhance blog functionality and SEO - Added a new '/blog' route to middleware for improved navigation. - Updated sitemap to dynamically include blog posts and series URLs. - Enhanced blog page metadata with Open Graph and Twitter card information. - Implemented structured data for blog posts and series to improve SEO. - Removed outdated blog post "Week 1: De Kickoff" from content. - Introduced optional OG image field in blog frontmatter for better sharing visuals. --- app/(marketing)/blog/page.tsx | 15 + .../blog/serie/[serieId]/[slug]/page.tsx | 110 +++++ app/(marketing)/blog/serie/[serieId]/page.tsx | 20 +- app/sitemap.ts | 32 ++ content/nl/blog/ai-speedrun/01-kickoff.mdx | 33 ++ .../ai-speedrun/02-authentication-docs.mdx | 48 ++ .../nl/blog/ai-speedrun/03-fhir-datamodel.mdx | 56 +++ content/nl/blog/ai-speedrun/04-de-valkuil.mdx | 62 +++ .../blog/ai-speedrun/05-spraakherkenning.mdx | 33 ++ .../blog/ai-speedrun/06-ai-tool-of-baas.mdx | 45 ++ .../nl/blog/ai-speedrun/07-ai-assistent.mdx | 54 +++ .../blog/ai-speedrun/08-week-4-spruitjes.mdx | 49 ++ .../09-verpleegkundige-overdracht.mdx | 51 ++ .../blog/ai-speedrun/10-stop-met-klagen.mdx | 65 +++ .../11-performance-optimalisatie.mdx | 47 ++ content/nl/blog/ai-speedrun/12-de-cijfers.mdx | 59 +++ content/nl/blog/ai-speedrun/13-learnings.mdx | 134 ++++++ .../nl/blog/ai-speedrun/week-1-kickoff.mdx | 18 - docs/blogs/blog-serie-speedrun.md | 455 ++++++++++++++++++ docs/seo-audit-aanbevelingen.md | 256 ++++++++++ lib/mdx/blog.ts | 1 + middleware.ts | 1 + 22 files changed, 1625 insertions(+), 19 deletions(-) create mode 100644 content/nl/blog/ai-speedrun/01-kickoff.mdx create mode 100644 content/nl/blog/ai-speedrun/02-authentication-docs.mdx create mode 100644 content/nl/blog/ai-speedrun/03-fhir-datamodel.mdx create mode 100644 content/nl/blog/ai-speedrun/04-de-valkuil.mdx create mode 100644 content/nl/blog/ai-speedrun/05-spraakherkenning.mdx create mode 100644 content/nl/blog/ai-speedrun/06-ai-tool-of-baas.mdx create mode 100644 content/nl/blog/ai-speedrun/07-ai-assistent.mdx create mode 100644 content/nl/blog/ai-speedrun/08-week-4-spruitjes.mdx create mode 100644 content/nl/blog/ai-speedrun/09-verpleegkundige-overdracht.mdx create mode 100644 content/nl/blog/ai-speedrun/10-stop-met-klagen.mdx create mode 100644 content/nl/blog/ai-speedrun/11-performance-optimalisatie.mdx create mode 100644 content/nl/blog/ai-speedrun/12-de-cijfers.mdx create mode 100644 content/nl/blog/ai-speedrun/13-learnings.mdx delete mode 100644 content/nl/blog/ai-speedrun/week-1-kickoff.mdx create mode 100644 docs/blogs/blog-serie-speedrun.md create mode 100644 docs/seo-audit-aanbevelingen.md 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 (
+ {/* Structured Data for SEO */} +