# How to Manage Blog Content with MDX Files Colocated with Route Components

> Easily manage blog content with MDX files colocated with route components in Next.js. Simplify your setup, leverage the file system for URLs, and achieve zero-runtime data fetching.

- Repository: [Ege Chelebi/blog](https://github.com/woosal1337/blog)
- Tags: how-to-guide
- Published: 2026-08-06

---

**Colocating MDX files with route components in a Next.js App Router structure allows you to manage blog posts as self-contained folders where the file system defines the URL, eliminating complex routing configuration and enabling zero-runtime data fetching.**

The `woosal1337/blog` repository demonstrates a content-as-code workflow using Next.js 14 App Router combined with MDX. By placing `page.mdx` files directly alongside their route components, this architecture creates a scalable system where each blog post lives in its own directory and automatically maps to a URL segment.

## The Folder-Per-Post Architecture

This repository implements a folder-per-post strategy under `app/(website)/blog/(post)/<slug>/`. Each blog entry resides in its own directory named after the URL slug, containing a single `page.mdx` file that serves as the route component.

When a user navigates to `/<slug>`, Next.js automatically renders the MDX file located at `app/(website)/blog/(post)/<slug>/page.mdx`. This file-system-based routing eliminates the need for manual route definitions, as the directory structure mirrors the site’s URL hierarchy exactly.

For example, the file `app/(website)/blog/(post)/vaulted/page.mdx` automatically becomes accessible at the `/vaulted` URL path.

## Configuring MDX Processing

The project centralizes MDX configuration in `lib/mdx-options.mjs`. This file registers the MDX loader and applies custom processing options, including remark and rehype plugins for enhanced Markdown functionality.

By abstracting the MDX configuration into a dedicated module, the repository maintains consistent processing rules across all blog posts. The configuration handles the transformation of Markdown syntax into React components while preserving the ability to embed JSX directly within content files.

## Customizing Components with mdx-components.tsx

All MDX content receives custom component overrides through [`mdx-components.tsx`](https://github.com/woosal1337/blog/blob/main/mdx-components.tsx). This file exports a component map that replaces standard HTML elements with optimized React components.

For instance, the repository overrides the default `img` tag to use Next.js’s optimized `Image` component:

```typescript
// mdx-components.tsx
import Image from 'next/image';
import ExternalLinkIcon from '@/components/ui/external-link-icon';

export const components = {
  img: (props) => <Image {...props} width={800} height={600} priority />,
  a: (props) => (
    <a {...props} target="_blank" rel="noopener noreferrer">
      {props.children} <ExternalLinkIcon />
    </a>
  ),
};

```

This approach guarantees consistent styling and behavior across all posts. Authors write standard Markdown syntax, but the output renders with responsive images and external-link indicators automatically applied.

## Dynamic Routing and Data Fetching

While individual posts use colocated MDX files, the repository still requires a dynamic route handler to process slugs. The file `app/(website)/blog/[slug]/page.tsx` (or the equivalent folder-based route) imports the MDX module for the matching slug and renders it as a React component.

Because the MDX file lives in the same folder as the route logic, import paths remain straightforward and type-safe. Next.js handles the compilation of MDX to React at build time, resulting in zero-runtime data fetching overhead.

### Static Generation Helpers

Helper functions in [`lib/blog.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog.ts) and [`lib/blog-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog-utils.ts) scan the filesystem at build time to generate navigation data. These utilities read the `app/(website)/blog/(post)` directory, extract frontmatter metadata using `gray-matter`, and expose post lists for the blog index and RSS feed.

The `getAllPosts` function in [`lib/blog-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog-utils.ts) demonstrates this pattern:

```typescript
// lib/blog-utils.ts
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

export function getAllPosts() {
  const postsDir = path.join(process.cwd(), 'app/(website)/blog/(post)');
  const slugs = fs.readdirSync(postsDir);
  return slugs.map((slug) => {
    const mdxPath = path.join(postsDir, slug, 'page.mdx');
    const file = fs.readFileSync(mdxPath, 'utf8');
    const { data } = matter(file);
    return { slug, ...data };
  });
}

```

This function returns an array of post objects containing slugs, titles, dates, and descriptions, enabling the blog index page to render a complete post list without client-side data fetching.

## Practical Implementation Examples

### Creating a New Post

To add a new blog entry, create a folder named after the desired URL slug:

1. Create the directory `app/(website)/blog/(post)/my-new-post/`

2. Add a `page.mdx` file with frontmatter:

```markdown
---
title: "My New Post"
date: "2026-08-06"
description: "An example of a new blog entry."
---

Welcome to my new post! Here’s some content written in MDX.

You can use **Markdown** syntax and JSX components.

```

3. No additional routing code is required. The dynamic route automatically detects the new folder and serves the content at `/my-new-post`.

### Accessing Post Metadata

The utility functions extract metadata from MDX frontmatter to power navigation and SEO features. The blog index page imports `getAllPosts` from [`lib/blog.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog.ts) to render a chronological list of entries, while the RSS generator uses the same data to produce syndication feeds.

## Benefits of Colocating MDX with Routes

- **Content-as-code workflow**: Authors edit a single MDX file while the file system determines the URL structure automatically.
- **Zero-runtime data fetching**: MDX compiles to React components at build time, eliminating client-side loading states and improving Core Web Vitals.
- **Self-contained posts**: Each blog entry includes its content, metadata, and implicit route configuration in one location, simplifying maintenance and archival.
- **Consistent component architecture**: Centralized overrides in [`mdx-components.tsx`](https://github.com/woosal1337/blog/blob/main/mdx-components.tsx) ensure uniform styling across all posts without requiring authors to remember implementation details.
- **Scalable navigation**: Adding content requires only creating a new folder; the static generation helpers automatically include new posts in indexes and feeds.

## Summary

Managing blog content with MDX files colocated with route components creates a maintainable, performant publishing system. The `woosal1337/blog` repository implements this through:

- Folder-per-post architecture under `app/(website)/blog/(post)/<slug>/` with `page.mdx` serving as the route component
- Centralized MDX configuration in `lib/mdx-options.mjs` for consistent processing
- Component overrides in [`mdx-components.tsx`](https://github.com/woosal1337/blog/blob/main/mdx-components.tsx) for optimized rendering
- Static generation helpers in [`lib/blog.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog.ts) and [`lib/blog-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog-utils.ts) for navigation data
- Zero-runtime overhead through build-time MDX compilation

This approach scales naturally as content grows, requiring no additional routing logic when adding new posts.

## Frequently Asked Questions

### What does colocating MDX files with route components mean?

Colocation refers to placing content files (MDX) in the same directory as their associated route logic. In this repository, each blog post lives in its own folder under `app/(website)/blog/(post)/<slug>/` alongside a `page.mdx` file that Next.js treats as the route component for that URL segment. This keeps content and routing logic tightly coupled and easy to locate.

### How does Next.js handle MDX files as route components?

Next.js 14 App Router natively supports MDX files as page components when configured with the appropriate loader. The `lib/mdx-options.mjs` configuration registers the MDX processor, allowing `page.mdx` files to export React components directly. At build time, Next.js compiles the Markdown and JSX into standard React components that render as static HTML.

### What is the role of mdx-components.tsx in this setup?

The [`mdx-components.tsx`](https://github.com/woosal1337/blog/blob/main/mdx-components.tsx) file provides a global component map that overrides default Markdown elements. When MDX content renders, it uses these custom components instead of standard HTML tags. This enables the repository to inject optimized Next.js `Image` components for images, add external-link icons to anchors, and apply consistent typography without modifying individual post files.

### How do I add a new blog post to this architecture?

Create a new folder under `app/(website)/blog/(post)/` named after your desired URL slug, then add a `page.mdx` file containing frontmatter metadata and content. The dynamic route automatically serves the new content at the corresponding URL, while the helper functions in [`lib/blog-utils.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog-utils.ts) automatically include the post in the blog index and RSS feed during the next build.