Files
cfw-autumn/apps/website/app/blog/[slug]/page.tsx
2026-04-27 16:31:10 +01:00

118 lines
3.4 KiB
TypeScript

import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";
import { MDXRemote } from "next-mdx-remote/rsc";
import { mdxComponents } from "@/components/blogComponents";
import { getAllPosts, getPostBySlug } from "@/lib/blogUtils";
import type { BlogParams } from "@/lib/types";
export function generateStaticParams() {
return getAllPosts().map((post) => ({ slug: post.slug }));
}
export async function generateMetadata({
params,
}: {
params: BlogParams;
}): Promise<Metadata> {
const { slug } = await params;
const post = getPostBySlug({ slug });
if (!post) return { title: "Post Not Found" };
return {
title: post.title,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
type: "article",
...(post.date ? { publishedTime: post.date } : {}),
authors: [post.author],
...(post.image && {
images: [{ url: post.image }],
}),
},
};
}
function formatDate(dateString: string | null) {
if (!dateString) return "";
return new Date(dateString).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
}
export default async function BlogPostPage({ params }: { params: BlogParams }) {
const { slug } = await params;
const post = getPostBySlug({ slug });
if (!post) notFound();
return (
<div className="py-16 md:py-24 bg-[#0F0F0F]">
<div className="max-w-[720px] mx-auto px-4 xl:px-0">
<Link
href="/blog"
className="inline-flex items-center gap-2 font-mono text-[12px] md:text-[14px] uppercase tracking-[-2%] text-[#FFFFFF66] hover:text-white transition-colors duration-300 mb-10"
>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
className="rotate-180"
>
<path
d="M6 3L11 8L6 13"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
Back to blog
</Link>
<header className="mb-12">
<div className="flex items-center gap-3 font-mono text-[12px] md:text-[14px] uppercase tracking-[-2%] text-[#FFFFFF66] mb-4">
<span>{formatDate(post.date)}</span>
<span className="w-1 h-1 bg-[#FFFFFF44] rounded-full" />
<span>{post.author}</span>
</div>
<h1 className="text-[30px] md:text-[40px] font-normal tracking-[-2%] leading-[1.1] font-sans text-white mb-4">
{post.title}
</h1>
{post.description && (
<p
className="text-[14px] md:text-[16px] leading-5 text-[#FFFFFF99] font-light font-sans [&_a]:text-[#9564ff] [&_a:hover]:text-[#b08aff] [&_a]:underline [&_a]:underline-offset-2 [&_a]:transition-colors"
// biome-ignore lint/security/noDangerouslySetInnerHtml: blog descriptions are trusted frontmatter content
dangerouslySetInnerHTML={{ __html: post.description }}
/>
)}
</header>
{post.image && (
<div className="relative w-full aspect-[2/1] overflow-hidden border border-[#292929] mb-12">
<Image
src={post.image}
alt={post.title}
fill
className="object-cover"
priority
sizes="(max-width: 768px) 100vw, 720px"
/>
</div>
)}
<hr className="border-[#292929] mb-12" />
<article className="prose prose-invert prose-lg max-w-none">
<MDXRemote source={post.source} components={mdxComponents} />
</article>
</div>
</div>
);
}