I wanted a blog I would still want to write in three years from now: fast, mine, and boring in all the right ways. Here is how this one is put together.
Posts are just files
Every post is an MDX file in content/posts. Frontmatter carries the metadata; the body is plain Markdown. No CMS, no database, fully version-controlled:
---
title: "How this blog is built"
date: "2026-06-12"
category: "engineering"
subcategory: "frontend"
tags: ["nextjs", "mdx", "tailwind"]
---
Posts are just files...
A small library reads the folder, parses frontmatter with gray-matter, computes reading time, and hands typed objects to the pages. Adding a post means adding a file — that is the whole publishing pipeline.
A taxonomy with two levels, plus tags
The structure is deliberately small: a handful of categories, each with one level of sub-categories, defined in a single file. Tags are free-form and cut across everything.
export const categories = [
{
slug: "engineering",
title: "Engineering",
subcategories: [
{ slug: "frontend", title: "Frontend" },
{ slug: "systems", title: "Systems & Backend" },
],
},
// ...
];
The rule I am holding myself to: do not add a bucket until a post actually needs it. Empty categories make a blog look abandoned.
Search, comments, likes
The three "interactive" features are each as light as they can be:
- Search is a static index over post metadata, fuzzy-matched in the browser with Fuse.js. No server, no Algolia bill.
- Comments are GitHub Discussions via Giscus. Signing in with GitHub kills drive-by spam and gives me moderation for free.
- Likes are a tiny API route backed by Redis in production, with a local file fallback so the button works in
devwith zero setup.
Why this stack
It compiles to mostly static pages, ships almost no JavaScript for an essay, and every piece is replaceable. The best part: the whole thing is one config file away from becoming a different person's blog — which is exactly how the second one in this little family of sites will get built.
Slow web, built to last.