{post.metadata.title}
{post.metadata.description}
import { getPostBySlug, getPostSlugs } 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: React.ReactNode): 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: React.ComponentPropsWithoutRef<'h1'>) =>
, h2: (props: React.ComponentPropsWithoutRef<'h2'>) => , h3: (props: React.ComponentPropsWithoutRef<'h3'>) => , p: (props: React.ComponentPropsWithoutRef<'p'>) => , ul: (props: React.ComponentPropsWithoutRef<'ul'>) =>,
pre: (props: React.ComponentPropsWithoutRef<'pre'>) => ,
a: (props: React.ComponentPropsWithoutRef<'a'>) => ,
SideNote,
Citation,
Bibliography,
};
type Props = {
params: Promise<{ slug: string }>;
};
export function generateStaticParams() {
return getPostSlugs()
.filter((slug) => slug.endsWith('.mdx'))
.map((slug) => ({ slug: slug.replace(/\.mdx$/, '') }));
}
export async function generateMetadata({ params }: Props) {
const { slug } = await params;
try {
const { metadata } = getPostBySlug(slug);
return {
title: metadata.title,
description: metadata.description,
openGraph: {
title: metadata.title,
description: metadata.description,
type: 'article' as const,
publishedTime: metadata.date,
},
twitter: {
card: 'summary' as const,
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}