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 full of overlapping data, and adding a third content type (blog) would have tripled the duplication and pushed every future content addition to 18+ file touches.

The solution: a content registry

Rather than keep maintaining parallel data pipelines, I built a single content registry that acts as the source of truth for every content query:

  • 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 gets populated at module load time from the existing per-type data files, so the migration carried no risk; the existing pages kept working exactly as before while I built the new abstraction layer 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 swapped the hard-coded if/else branches in the metadata builder for a plain config lookup, so the system extends without any 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 worth reading. If it takes six files, the architecture is paying tax on every addition, and each one quietly raises the price of the next. 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. So design for the access pattern, not for the first type that happened to ship.