This Blog is Made of Things
A Next.js blog with local MDX, no CMS, no external dependencies. Posts are files, git is the history, deploy is a push.
The stack#
Nothing exotic here:
- Next.js 15 (App Router) + React 19
- Tailwind CSS v4 with @tailwindcss/typography for prose
- MDX for posts, sugar-high for syntax highlighting
- Biome for linting and formatting
- Vercel for hosting
No Turborepo. It's one site, not a monorepo.
Why local MDX over a CMS?#
I tried Hashnode as a headless CMS first (inspired by Colby Fayock's tutorial a while back). Didn't last long. Too many moving parts for what's basically a text file with some metadata on top.
Local MDX is better for this. Posts are just files in the repo. Git is the version history. No API keys, no external services, no surprises.
The trade-off is real though: no web-based editor, no scheduled publishing, no collaboration workflow. If someone else needed to contribute, they'd need to know git. For a solo blog that's fine. I'm in my editor anyway.
The "CMS" is a lib/posts.ts that reads .mdx files and parses frontmatter with gray-matter. The page component compiles the MDX with @mdx-js/mdx at render time. @next/mdx handles the build pipeline side. About 60 lines total.
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";
const postsDirectory = path.join(process.cwd(), "content/posts");
// Simplified: the real version also filters drafts and sorts by date
const posts = fs
.readdirSync(postsDirectory)
.filter((name) => name.endsWith(".mdx"))
.map((fileName) => {
const { data, content } = matter(
fs.readFileSync(path.join(postsDirectory, fileName), "utf8"),
);
return { slug: fileName.replace(/\.mdx$/, ""), frontmatter: data, content };
});
// Returns: [{ slug: "my-post", frontmatter: { title, date, ... }, content: "..." }]
Looks like nothing, on purpose#
Minimal on purpose. No sidebar, no newsletter popup. Cover images exist but most posts skip them. I don't care much for comments or share-on-socials links.
Typography is Geist for body and Geist Mono for code. Dark mode has a toggle in the nav and also picks up your system preference. That's about it.
The design borrows heavily from some famous internet personalities and important figures, e.g. Lee Robinson's blog: clean, typography-driven, fast. If something looks familiar, that's probably why.
What's next#
- RSS feed
- OG images for link previews
- Cross-posting to Hashnode (canonical stays here)
Works well enough. Time to actually write things.
Further reading#
- Lee Robinson's blog, the design inspiration, err, some of it, in the beginning 😄
- MDX docs, the content format
- gray-matter for frontmatter parsing
- sugar-high for lightweight syntax highlighting