{post.metadata.title}
{post.metadata.description}
import { getPostBySlug } from '@/lib/mdx'; import { MDXRemote } from 'next-mdx-remote/rsc'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { format } from 'date-fns'; import rehypeSlug from 'rehype-slug'; import rehypeAutolinkHeadings from 'rehype-autolink-headings'; import { TableOfContents } from '@/components/mdx/TableOfContents'; import { SideNote } from '@/components/mdx/SideNote'; import { Citation, Bibliography } from '@/components/mdx/Citation'; import { MobileTableOfContents } from '@/components/mdx/MobileTableOfContents'; // Utility to ensure consistent IDs const slugify = (text: any): string => { if (!text) return ''; const str = typeof text === 'string' ? text : String(text); return str .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); }; const components = { h1: (props: any) =>
, h2: (props: any) => , h3: (props: any) => , p: (props: any) => , ul: (props: any) =>,
pre: (props: any) => ,
a: (props: any) => ,
SideNote,
Citation,
Bibliography,
};
type Props = {
params: Promise<{ slug: string }>;
};
export async function generateMetadata({ params }: Props) {
const { slug } = await params;
try {
const { metadata } = getPostBySlug(slug);
return {
title: metadata.title,
description: metadata.description,
};
} catch {
return {
title: 'Post Not Found',
};
}
}
export default async function BlogPost({ params }: Props) {
const { slug } = await params;
let post;
try {
post = getPostBySlug(slug);
} catch {
notFound();
}
// Extract headings for TOC
const headingLines = post.content.match(/^#{2,3}\s+(.+)$/gm) || [];
const headings = headingLines.map((line) => {
const level = line.match(/^#+/)?.[0].length || 2;
const text = line.replace(/^#+\s+/, '').trim();
const id = slugify(text);
return { id, text, level };
});
return (
{post.metadata.description}