Build Log Entry
Building the MDX Blog System
How I set up MDX-powered posts with frontmatter parsing, slug generation, and a utility layer that keeps the rest of the app clean.
title: "Building the MDX Blog System" date: "2025-01-22" description: "How I set up MDX-powered posts with frontmatter parsing, slug generation, and a utility layer that keeps the rest of the app clean." tags: ["mdx", "next.js", "typescript", "architecture"]
Building the MDX Blog System
Every developer blog needs a content system. The question is whether you reach for a headless CMS, a third-party platform, or build your own. I went with my own — simple, file-based, MDX-powered.
Here's exactly how it works.
Why MDX?
MDX lets you write Markdown with embedded React components. That means:
- Long-form prose stays readable in the source files
- I can drop in custom components (code blocks, callouts, diagrams) without leaving the flow of writing
- Everything lives in git — no databases, no external dependencies, no vendor lock-in
The tradeoff is that setup takes more work upfront. Worth it.
The File Structure
Posts live in src/content/build-log/. Each file is named with a zero-padded index and a slug:
src/content/build-log/
001-launching-the-late-night-project.mdx
002-building-the-mdx-blog-system.mdx
003-design-system-foundations.mdx
The numbering keeps them ordered chronologically in the file system, which is nice when you have a lot of posts.
Frontmatter Schema
Each post has a consistent frontmatter block:
---
title: "Post Title Here"
date: "YYYY-MM-DD"
description: "One or two sentence summary for SEO and previews."
tags: ["tag1", "tag2", "tag3"]
---
- title — Display title, also used for
<title>and OpenGraph - date — ISO 8601 date string for sorting and display
- description — SEO meta description and card preview text
- tags — Array of string tags for filtering and categorization
The Utility Layer
The src/lib/posts.ts file exposes a clean API:
// Get all posts sorted by date (newest first)
getAllPosts(): BuildLogPost[]
// Get a single post by slug with full content
getPostBySlug(slug: string): BuildLogPost | null
Under the hood, it uses gray-matter to parse frontmatter from the MDX files and fs to read from disk at build time. The content stays as a raw string until the page component needs to render it.
What's Next
The utility layer is in place. Next up is building the actual /build-log page that lists all posts, and individual post pages that render the MDX content with a custom component set.
The interesting challenge there will be the code syntax highlighting. I'm looking at Shiki for that — it runs at build time and ships zero JavaScript for highlighting, which is the right call for a static-leaning site.