From 5b3e24ca7834f53ac0983fd7d2ea88f4e0fe6c6b Mon Sep 17 00:00:00 2001 From: kbot Date: Mon, 6 Jul 2026 20:22:27 -0500 Subject: [PATCH] feat(blog): add /blog routes with dedicated editorial layout --- app/blog/[slug]/page.tsx | 88 ++++++++++++++++++++++++++++++++++++ app/blog/layout.tsx | 16 +++++++ app/blog/page.tsx | 30 ++++++++++++ app/blog/tags/[tag]/page.tsx | 61 +++++++++++++++++++++++++ app/blog/template.tsx | 15 ++++++ 5 files changed, 210 insertions(+) create mode 100644 app/blog/[slug]/page.tsx create mode 100644 app/blog/layout.tsx create mode 100644 app/blog/page.tsx create mode 100644 app/blog/tags/[tag]/page.tsx create mode 100644 app/blog/template.tsx diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..6637a89 --- /dev/null +++ b/app/blog/[slug]/page.tsx @@ -0,0 +1,88 @@ +import { notFound } from 'next/navigation' +import Link from 'next/link' +import { getPosts, getPost } from '@/lib/posts' +import { tagToSlug } from '@/lib/tags' +import { TableOfContents } from '@/components/posts/TableOfContents' +import { ScrollToTop } from '@/components/ui/ScrollToTop' +import { ReadingProgress } from '@/components/ui/ReadingProgress' + +export const dynamicParams = false +export const dynamic = 'force-static' + +export async function generateStaticParams() { + const posts = await getPosts() + if (posts.length === 0) { + return [{ slug: '__no-posts__' }] + } + return posts.map((post) => ({ slug: post.slug })) +} + +export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) { + const slug = (await params).slug + const post = await getPost(slug) + if (!post) return { title: 'Not Found' } + return { title: post.title } +} + +export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) { + const slug = (await params).slug + + if (slug === '__no-posts__') notFound() + + const post = await getPost(slug) + + if (!post) notFound() + + const { default: PostContent } = await import( + /* turbopackOptional: true */ + `@/content/posts/${slug}.mdx` + ) + + return ( + <> + + +
+
+
+
+ +

+ {post.title} +

+
+ {post.author && by {post.author}} + {post.readingTime} min read +
+ {post.coverImage && ( + {`Featured + )} + {post.tags.length > 0 && ( +
+ {post.tags.map((tag) => ( + + {tag} + + ))} +
+ )} +
+
+ +
+
+ +
+
+ + ) +} diff --git a/app/blog/layout.tsx b/app/blog/layout.tsx new file mode 100644 index 0000000..fd556a7 --- /dev/null +++ b/app/blog/layout.tsx @@ -0,0 +1,16 @@ +import { Header } from '@/components/layout/Header' +import { Footer } from '@/components/layout/Footer' +import { ScrollProgress } from '@/components/ui/ScrollProgress' + +export default function BlogLayout({ children }: { children: React.ReactNode }) { + return ( + <> + +
+
+ {children} +
+