Here's how this site works. Not because it's particularly novel (it's a Next.js blog), but because writing it down is how I figure out if I actually understand what I built.
The stack
- Next.js 16 with the App Router and static generation
- TypeScript throughout
- Tailwind CSS v4 with CSS-first configuration (
@theme inline) - MDX for blog posts, parsed with
gray-matterand rendered withnext-mdx-remote/rsc - Dark-only. No toggle, no light mode
- Deployed on Vercel with a custom domain
Design system
The visual language is documented in the brand book: typography, colour tokens, components, and the retro layer. Go look at it, it's pretty.
Blog architecture
Posts live as .mdx files in /content. Each file has YAML frontmatter:
---
title: "Post Title"
date: "2026-02-13"
description: "SEO description under 160 chars."
tags: ["builds", "meta"]
---
At build time, gray-matter parses frontmatter and next-mdx-remote/rsc renders the MDX as a React Server Component. The blog index reads all files, sorts by date, and statically generates each page via generateStaticParams:
export async function generateStaticParams() {
const posts = getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
No CMS, no database, no API. Just files in a repo.
Dark-only
No theme toggle. No light mode. The <html> tag gets className="dark" and that's it. CSS custom properties in :root define the palette once; Tailwind v4 maps them via @theme inline. One colour scheme to maintain, zero runtime JS for theming.
It started with next-themes and a toggle, but having two palettes meant testing two palettes. Removing it cut a dependency, deleted two components, and simplified the CSS. Dark felt right for a site called testing in production.