This commit is contained in:
Timo Knuth
2026-01-28 16:02:32 +01:00
parent 1a6dc01291
commit a76bcb70e1
5 changed files with 107 additions and 3 deletions

View File

@@ -4,18 +4,41 @@ import { getPublishedPostBySlug, getAuthorBySlug, getRelatedPosts, getPublishedP
import { AnswerBox } from "@/components/aeo/AnswerBox";
import { StepList } from "@/components/aeo/StepList";
import { FAQSection } from "@/components/aeo/FAQSection";
import { SourcesList } from "@/components/aeo/SourcesList";
import { AuthorCard } from "@/components/author/AuthorCard";
import { RelatedPosts } from "@/components/blog/RelatedPosts";
import { blogPostingSchema, howToSchema, faqPageSchema } from "@/lib/schema";
import { blogPostingSchema, howToSchema, faqPageSchema, breadcrumbSchema } from "@/lib/schema";
import Image from "next/image";
import Link from "next/link";
export function generateMetadata({ params }: { params: { slug: string } }) {
const post = getPublishedPostBySlug(params.slug);
if (!post) return {};
const ogImage = post.heroImage ? `https://www.qrmaster.net${post.heroImage}` : undefined;
return {
title: post.title,
description: post.description,
alternates: {
canonical: `https://www.qrmaster.net/blog/${post.slug}`,
},
openGraph: {
title: post.title,
description: post.description,
type: 'article',
publishedTime: post.datePublished,
modifiedTime: post.dateModified || post.datePublished,
authors: ['https://www.qrmaster.net'],
tags: post.keywords,
images: ogImage ? [{ url: ogImage, alt: post.imageAlt || post.title }] : undefined,
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.description,
images: ogImage ? [ogImage] : undefined,
}
};
}
@@ -38,6 +61,15 @@ export default function BlogPostPage({ params }: { params: { slug: string } }) {
const howtoLd = post.keySteps?.length ? howToSchema(post, author) : null;
const faqLd = post.faq ? faqPageSchema(post.faq) : null;
// Generate breadcrumb schema: Home → Learn → Pillar → Post
const pillarName = post.pillar ? post.pillar.charAt(0).toUpperCase() + post.pillar.slice(1) : 'Blog';
const breadcrumbLd = breadcrumbSchema([
{ name: 'Home', url: '/' },
{ name: 'Learn', url: '/learn' },
{ name: pillarName, url: `/learn/${post.pillar}` },
{ name: post.title, url: `/blog/${post.slug}` },
]);
return (
<main className="container mx-auto max-w-4xl py-12 px-4">
<Script id="ld-blogposting" type="application/ld+json" strategy="afterInteractive"
@@ -50,6 +82,8 @@ export default function BlogPostPage({ params }: { params: { slug: string } }) {
<Script id="ld-faq" type="application/ld+json" strategy="afterInteractive"
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqLd) }} />
)}
<Script id="ld-breadcrumb" type="application/ld+json" strategy="afterInteractive"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }} />
<header className="space-y-6 text-center max-w-3xl mx-auto mb-10">
<div className="flex justify-center gap-2 text-sm text-gray-500 font-medium">
@@ -71,7 +105,12 @@ export default function BlogPostPage({ params }: { params: { slug: string } }) {
)}
<div className="text-left text-sm">
<div className="font-bold text-gray-900">{author.name}</div>
<div className="text-gray-500">Updated {post.updatedAt || post.date}</div>
<div className="text-gray-500 text-xs mt-0.5">
Published {post.date}
{post.updatedAt && (
<> <span className="mx-1"></span> Updated {post.updatedAt}</>
)}
</div>
</div>
</div>
)}
@@ -97,6 +136,9 @@ export default function BlogPostPage({ params }: { params: { slug: string } }) {
{/* AEO BLOCK: FAQ */}
{!!post.faq?.length && <div className="mt-12"><FAQSection items={post.faq} /></div>}
{/* AEO BLOCK: SOURCES */}
{!!post.sources?.length && <SourcesList sources={post.sources} />}
<div className="border-t border-gray-100 my-12"></div>
{author && <AuthorCard author={author} />}