Building a Modern Blog with Next.js and MDX
In this post, I'll walk through the technical decisions behind building this blog. If you're looking to create a similar setup, this should give you a solid starting point.
Why a Rewrite?
Sometimes you look at a project you built a few years ago and think, "yeah, it's time." That was me with my personal website. The old version had served me well, but it was starting to show its age. Dependencies were getting stale, the codebase had accumulated some cruft, and honestly, I just wanted an excuse to play with newer tech.
There's something refreshing about starting from scratch. No legacy decisions to work around, no "I'll fix that later" comments haunting you from 2011. Just a blank canvas and the freedom to do things the way you'd do them today.
The web moves fast. What was considered best practice a couple of years ago might now have better alternatives. React Server Components, Tailwind v4's new CSS-first approach, improved build tooling — I wanted to take advantage of all of it. Plus, let's be honest, there's a certain satisfaction in seeing those fresh, zero-vulnerability npm audit results.
So here we are. A ground-up rebuild using the latest and greatest. Was it strictly necessary? Probably not. Was it worth it? Absolutely.
Goals
When I set out to build this site, I had a few key requirements:
- Static export — No server required, can be hosted anywhere
- Markdown-based content — Easy to write and version control
- Modern stack — Latest versions of Next.js and React
- Minimal dependencies — Only what's necessary
- Fast and accessible — Performance matters
The Stack
Next.js 16 with Static Export
Next.js provides an excellent developer experience and the output: 'export' option makes it trivial to generate a fully static site:
// next.config.ts
const nextConfig = {
output: 'export',
images: {
unoptimized: true,
},
trailingSlash: true,
};MDX for Content
MDX allows writing content in Markdown while supporting React components. This means I can embed interactive elements when needed:
Regular markdown text with **bold** and _italic_.
<Callout type="info">But also custom React components like this callout!</Callout>The key packages are:
gray-matter— Parses frontmatter from MDX filesnext-mdx-remote— Compiles and renders MDX at build timerehype-pretty-code— Syntax highlighting with Shiki
Tailwind CSS v4
Tailwind v4 introduces a new CSS-first configuration approach. Instead of a JavaScript config file, you define your theme directly in CSS:
@import 'tailwindcss';
@theme inline {
--color-background: #fafafa;
--color-foreground: #0a0a0a;
/* ... */
}Blog Architecture
Content Organization
Blog posts live in /content/blog/ as MDX files:
content/
└── blog/
├── hello-world.mdx
└── building-a-modern-blog.mdx
Fetching Posts
A utility function reads and parses all posts at build time:
export function getAllPosts(): BlogPost[] {
const files = fs.readdirSync(BLOG_DIR);
return files
.filter((file) => file.endsWith('.mdx'))
.map((file) => {
const { data, content } = matter(fileContent);
return {
slug: file.replace('.mdx', ''),
frontmatter: data,
content,
readingTime: readingTime(content).text,
};
})
.filter((post) => post.frontmatter.published)
.sort((a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date));
}Static Generation
Next.js generates all blog post pages at build time using generateStaticParams:
export async function generateStaticParams() {
const posts = getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}Deployment
The site is deployed to Cloudflare Pages, which offers:
- Free hosting for static sites
- Automatic deployments from Git
- Global CDN
- Custom domain support
The build process is simple:
pnpm build
# Output goes to /out directoryConclusion
This setup provides a great balance of developer experience, performance, and simplicity. The entire site builds in seconds, loads instantly for users, and is easy to maintain.
Feel free to explore the source code for the full implementation.