From 5b07fae911aaf77016dcfa94363d2384dc172a09 Mon Sep 17 00:00:00 2001 From: nicholai Date: Tue, 7 Oct 2025 21:52:21 -0600 Subject: [PATCH] feat(blog): Medium-style redesign with PostCard grid, Prose typography; PostHeader + reading progress; back links; reading time/excerpt utils; GitHub-backed MDX provider; prev/next navigation; theme-aligned cards --- app/blog/[slug]/page.tsx | 74 +++++++--- app/blog/page.tsx | 27 +++- app/blog/utils.ts | 232 ++++++++++++++++++++++++++++---- app/globals.css | 118 ++++++++++++++++ app/page.tsx | 2 +- components/blog/PostCard.tsx | 65 +++++++++ components/blog/PostHeader.tsx | 57 ++++++++ components/blog/ProgressBar.tsx | 34 +++++ 8 files changed, 556 insertions(+), 53 deletions(-) create mode 100644 components/blog/PostCard.tsx create mode 100644 components/blog/PostHeader.tsx create mode 100644 components/blog/ProgressBar.tsx diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx index 297c2fe..653e8e3 100644 --- a/app/blog/[slug]/page.tsx +++ b/app/blog/[slug]/page.tsx @@ -1,10 +1,13 @@ +import Link from 'next/link' import { notFound } from 'next/navigation' import { CustomMDX } from '@/components/mdx' -import { formatDate, getBlogPosts } from '../utils' +import PostHeader from '@/components/blog/PostHeader' +import ProgressBar from '@/components/blog/ProgressBar' +import { getAllPosts, getReadingTime, findAdjacentPosts } from '../utils' import { baseUrl } from '../../sitemap' export async function generateStaticParams() { - let posts = getBlogPosts() + const posts = await getAllPosts() return posts.map((post) => ({ slug: post.slug, @@ -12,10 +15,10 @@ export async function generateStaticParams() { } export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) { - const { slug } = await params; - const post = getBlogPosts().find((post) => post.slug === slug) + const { slug } = await params + const post = (await getAllPosts()).find((p) => p.slug === slug) if (!post) { - return + return {} } const { @@ -24,6 +27,7 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str summary: description, image, } = post.metadata + const ogImage = image ? image : `${baseUrl}/og?title=${encodeURIComponent(title)}` @@ -37,11 +41,7 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str type: 'article', publishedTime, url: `${baseUrl}/blog/${post.slug}`, - images: [ - { - url: ogImage, - }, - ], + images: [{ url: ogImage }], }, twitter: { card: 'summary_large_image', @@ -54,14 +54,29 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str export default async function Blog({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; - const post = getBlogPosts().find((post) => post.slug === slug) + const posts = await getAllPosts() + const post = posts.find((p) => p.slug === slug) if (!post) { notFound() } + const reading = getReadingTime(post.content) + const { prev, next } = findAdjacentPosts(posts, slug) + return ( -
+ <> + +
+
+ + ← Back to blog + +