SKIP TO CONTENT
BACK TO BLOG LOGS
May 20, 2026|3 min read|Web Architecture

Behind the Code: Why I Built a Custom MDX Blog

How a quest for zero dependencies and instant load times led to a lightweight Markdown engine built on Next.js Server Components.

When I set out to build this portfolio, I wanted it to reflect my core engineering philosophy: simplicity, efficiency, and zero bloat. Most developers today default to heavy cloud databases, headless CMS providers, or complex third-party SaaS integrations just to host a few written articles.

Instead of adding another API key and runtime overhead to my setup, I decided to build a custom, file-system-driven Markdown engine from scratch.

Here is how I designed it, and why this architectural choice matters.

The Architecture: Keeping It Local-First

The entire blog system is powered by Next.js Server Components and dynamic Static Site Generation (SSG).

Rather than fetching posts at runtime from an external server (which introduces latency and connection risks), all posts are authored locally inside the codebase as flat .md files. This architecture uses a three-stage build pipeline:

  1. Server-Side MDX Parser: A custom module in src/lib/mdx.ts uses gray-matter to parse each Markdown file at build time, extracting metadata (frontmatter) and content strings without any database handshakes.
  2. Dynamic Route Prerendering: Next.js uses the generateStaticParams API to crawl the filesystem, identify available slugs, and precompile every single blog post into pure, static HTML.
  3. Static Page Serving: When a visitor clicks on an article, the server delivers the prerendered HTML instantly. No database calls, no client-side hydration loops, and no layout shifts.

Engineering the Code: Clean & Lean

Here is the exact server-side parser implementation that handles directory crawling and frontmatter parsing:

// src/lib/mdx.ts - Extracting blog posts dynamically at build time
export function getAllBlogPosts(): BlogPost[] {
  const blogDir = path.join(process.cwd(), "content", "blog");
  
  if (!fs.existsSync(blogDir)) return [];

  const files = fs.readdirSync(blogDir);
  
  return files
    .filter((file) => file.endsWith(".md") || file.endsWith(".mdx"))
    .map((file) => {
      const slug = file.replace(/\.mdx?$/, "");
      const fullPath = path.join(blogDir, file);
      const fileContents = fs.readFileSync(fullPath, "utf8");
      const { data, content } = matter(fileContents);

      return {
        slug,
        content,
        title: data.title || "",
        description: data.description || "",
        date: data.date || "",
        readTime: data.readTime || "5 min read",
        category: data.category || "Engineering",
        status: data.status || "DRAFT",
      };
    })
    .filter((post) => post.status === "PUBLISHED")
    .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
}

Why This Matters

By building a self-contained, Markdown-based blog engine, I achieved three core goals:

  • Absolute Speed: Because pages are fully static, the time-to-first-byte (TTFB) is virtually instant, maximizing Lighthouse performance scores.
  • Zero-Maintenance Security: There is no database to secure, no SQL injections to mitigate, and no CMS logins to manage.
  • Local-First Simplicity: Adding a new post is as simple as creating a new .md file, committing it, and pushing it to Git.

For me, software engineering is about finding the most elegant and minimal solution to a problem. Sometimes, that means writing a few clean parser functions rather than pulling in an entire CMS ecosystem.

RETURN TO BLOGS