Skip to main content

Building a Unified Content Pipeline with Next.js and MDX

An abstract network of connected nodes glowing in blue
Apr 3, 20262 min readNext.js, MDX, TypeScript

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 and make every future content addition cost 18+ file touches.

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

The Lesson

The cost of adding a new content type is a signal. If it takes six files, the architecture is paying taxes on every addition, and every addition compounds the next one's cost. A single registry with a typed config flips the math: adding a content type is three files, and the existing query API already works on day one. Design for the access pattern, not for the first type that shipped.