import { notFound } from "next/navigation"; import type { Metadata } from "next"; import dynamic from "next/dynamic"; import type { BlogPost } from "../../../../lib/content"; import { getBlogPostBySlug, getAllBlogPosts as getAllPosts, getRelatedBlogPosts, } from "../../../../lib/content"; import { logger } from "../../../../lib/logger"; import ContentBanner from "../../../components/sections/ContentBanner"; import AskOrganizer from "../../../components/sections/AskOrganizer"; import { getAssetPath, ASSETS } from "../../../../lib/assetUtils"; import "../blog.css"; // Code split RelatedArticles - blog-specific, below the fold const RelatedArticles = dynamic( () => import("../../../components/sections/RelatedArticles"), { loading: () => (
), ssr: true, }, ); // AskOrganizer data - same as index page const askOrganizerData = { title: "Still have questions?", subtitle: "Get answers from an experienced organizer", buttonText: "Ask an organizer", }; interface PageProps { params: Promise<{ slug: string }>; } /** * Generate static params for all blog posts * This enables static generation for all blog posts at build time */ export async function generateStaticParams() { try { const posts = getAllPosts(); return posts.map((post) => ({ slug: post.slug, })); } catch (error) { logger.error("Error generating static params:", error); return []; } } /** * Generate metadata for each blog post */ export async function generateMetadata({ params, }: PageProps): Promise { try { const { slug } = await params; const post = getBlogPostBySlug(slug); if (!post) { return { title: "Post Not Found", description: "The requested blog post could not be found.", }; } return { title: post.frontmatter.title, description: post.frontmatter.description, authors: [{ name: post.frontmatter.author }], openGraph: { title: post.frontmatter.title, description: post.frontmatter.description, type: "article", publishedTime: post.frontmatter.date, authors: [post.frontmatter.author], url: `https://communityrule.com/blog/${slug}`, siteName: "CommunityRule", }, twitter: { card: "summary_large_image", title: post.frontmatter.title, description: post.frontmatter.description, creator: "@communityrule", }, }; } catch (error) { logger.error("Error generating metadata:", error); return { title: "Blog Post", description: "A blog post from our community.", }; } } /** * Dynamic blog post page */ export default async function BlogPostPage({ params }: PageProps) { // Get the blog post data const { slug } = await params; const post = getBlogPostBySlug(slug); // If post doesn't exist, show 404 if (!post) { notFound(); } // Get related articles with improved algorithm const allPosts = getAllPosts(); const slugOrder = allPosts.map((post) => post.slug); const relatedArticles = getRelatedBlogPosts( post.slug, post.frontmatter.related, 3, ); // Generate structured data for search engines const structuredData = { "@context": "https://schema.org", "@type": "Article", headline: post.frontmatter.title, description: post.frontmatter.description, author: { "@type": "Person", name: post.frontmatter.author, }, publisher: { "@type": "Organization", name: "CommunityRule", url: "https://communityrule.com", logo: { "@type": "ImageObject", url: "https://communityrule.com/assets/logo/Logo.svg", }, }, datePublished: post.frontmatter.date, dateModified: post.frontmatter.date, mainEntityOfPage: { "@type": "WebPage", "@id": `https://communityrule.com/blog/${post.slug}`, }, url: `https://communityrule.com/blog/${post.slug}`, articleSection: "Community Building", keywords: ["community", "governance", "decision making", "collaboration"], }; // Breadcrumb structured data const breadcrumbData = { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [ { "@type": "ListItem", position: 1, name: "Home", item: "https://communityrule.com", }, { "@type": "ListItem", position: 2, name: "Blog", item: "https://communityrule.com/blog", }, { "@type": "ListItem", position: 3, name: post.frontmatter.title, item: `https://communityrule.com/blog/${post.slug}`, }, ], }; // Get article-specific background color from frontmatter const getBackgroundColor = (post: BlogPost): string => { if (post.frontmatter?.background?.color) { return post.frontmatter.background.color; } return "#1F2937"; // Default fallback (dark gray) }; const backgroundColor = getBackgroundColor(post); return ( <> {/* Structured Data */}