Why I Migrated My Blog From Sanity CMS to Local MDX
When I first built my portfolio, I did what almost every modern developer tutorial suggests: I hooked it up to a headless CMS. I chose Sanity, and for a while, it was fantastic. It gave me a beautiful studio interface, structured content, and powerful querying.
But as time went on, I realized I had fallen into a common trap: over-engineering a simple problem.
Here is a deep dive into why I decided to tear out my CMS and replace it with a dead-simple, local MDX architecture.
The Problem with a Headless CMS for Personal Blogs
If you are running a company blog with multiple non-technical authors, a headless CMS is absolutely essential. But if you are a solo developer writing technical content, the overhead quickly outweighs the benefits:
- Context Switching: To write a post, I had to leave my code editor, log into a web dashboard, write my content, and then switch back to my terminal to test it locally.
- Dependency Bloat: My
package.jsonwas filled with CMS-specific libraries (@sanity/client,@sanity/image-url,next-sanity,@portabletext/react). - Build Complexity: Fetching data at build time meant dealing with API rate limits, webhooks for triggering deployments, and environment variables.
- Vendor Lock-in: My content was trapped in a proprietary JSON format. Migrating away required writing a script to export and convert everything.
The MDX Solution
I wanted a system that felt native to a developer's workflow. The solution was MDX — Markdown with embedded JSX components.
By moving to local .mdx files stored directly in my GitHub repository, I achieved several immediate benefits:
1. Git is the Source of Truth
My content is now version-controlled alongside my code. Every blog post is a commit. If I want to roll back a change, I just use git revert.
2. Zero-Friction Authoring
I write my posts in VS Code. I get GitHub Copilot assistance, syntax highlighting for my code snippets, and I don't have to log into any third-party dashboard.
3. Ultimate Performance
Because the files are local, Next.js can read them directly from the filesystem during the build step using fs.readFileSync. There are no network requests, no API latency, and no rate limits. The build is practically instantaneous.
How It Works Under the Hood
The setup is shockingly simple. I use @next/mdx alongside a few unified plugins:
// next.config.mjs
import createMDX from "@next/mdx";
import remarkGfm from "remark-gfm";
import remarkFrontmatter from "remark-frontmatter";
const withMDX = createMDX({
options: {
remarkPlugins: [remarkGfm, remarkFrontmatter],
rehypePlugins: [], // add rehype plugins here!
},
});
export default withMDX({
pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
});
For metadata, I use gray-matter to parse YAML frontmatter at the top of each file, allowing me to easily extract titles, dates, and categories for my blog listing page.
The Result
The migration allowed me to delete over 500 lines of complex data-fetching code and configuration. My portfolio is now entirely self-contained.
Sometimes, the best architectural decision you can make is to remove moving parts. For a developer portfolio, local markdown is exactly what it needs to be: simple, fast, and completely under your control.
Abhishek Sharma
Full Stack Engineer