Skip to main content

Building a Unified Content Pipeline with Next.js and MDX

Apr 3, 20261 min readNext.js, MDX, TypeScript, Content Architecture

The Problem

Every content type on this portfolio — projects and experience — had its own slug file, thumbnail file, MDX import index, content ordering array, reading time map, and structured data file. Adding a single project meant touching six files with overlapping data. Adding a third content type (blog) would triple the duplication.

The Solution: A Content Registry

Instead of maintaining parallel data pipelines, I created a single content registry that serves as the source of truth for all content queries:

  • getContentByType('project') — ordered array for listing pages
  • getContentBySlug('blog', 'my-post') — single entry for detail pages
  • getContentSlugs('experience') — for generateStaticParams
  • getContentPagination('blog', slug) — circular prev/next navigation

The registry is populated at module load time from the existing per-type data files, so the migration was zero-risk — existing pages continued to work identically while the new abstraction layer was built on top.

Type-Safe Content Configs

Each content type has a config entry that maps it to its URL base path, display label, and content directory:

const contentTypeConfigs = {
  project: { basePath: '/projects', label: 'Project', contentDir: 'projects' },
  experience: {
    basePath: '/experience',
    label: 'Experience',
    contentDir: 'experience',
  },
  blog: { basePath: '/blog', label: 'Blog', contentDir: 'blog' },
};

This replaced hard-coded if/else branches in the metadata builder with a simple config lookup, making the system extensible without code changes.

Results

  • Adding a new blog post: 3 files instead of 6+
  • Zero runtime changes to existing pages
  • 18 new registry tests covering all query paths
  • Type-safe throughout — ContentType union prevents typos