# How to Set Up a Next.js 14 App Router Blog with MDX and TypeScript

> Learn to set up a Next.js 14 App Router blog with MDX and TypeScript. Build a robust data layer, implement dynamic metadata, and optimize your blog for SEO.

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

---

**Setting up a Next.js 14 App Router blog requires configuring the root layout with global providers, creating a data layer to parse MDX files, and implementing dynamic metadata generation for SEO.**

The woosal1337/blog repository demonstrates a production-ready implementation of a modern blog using **Next.js 14 App Router**, **TypeScript**, and **MDX** content. This architecture leverages static generation at build time to deliver optimal performance while maintaining type safety throughout the entire stack.

## Project Architecture Overview

The codebase follows a three-layer architecture that separates layout concerns, data fetching, and SEO configuration into distinct modules.

### Root Layout and Global Providers

The [`app/layout.tsx`](https://github.com/woosal1337/blog/blob/main/app/layout.tsx) file establishes the HTML skeleton and wraps the application with essential providers for theme management, tooltips, sound effects, and Vercel Analytics. It imports site-wide metadata constants from [`lib/seo.ts`](https://github.com/woosal1337/blog/blob/main/lib/seo.ts) to configure default Open Graph and Twitter card properties.

```tsx
// app/layout.tsx
export const metadata = {
  metadataBase: new URL(SITE_URL),
  title: { default: SITE_NAME, template: `%s | ${SITE_NAME}` },
  description: SITE_DESCRIPTION,
  // ... additional SEO configuration
};

```

### Blog Index Page

The entry point for the blog lives at `app/(website)/blog/page.tsx`. This server component fetches post metadata using `getAllPosts()` from the data layer and constructs page-level SEO metadata via `createPageMetadata()`.

```tsx
// app/(website)/blog/page.tsx
import { createPageMetadata } from '@/lib/seo';
import { getAllPosts } from '@/lib/blog';

export const metadata = createPageMetadata({
  title: 'AI Engineering, Agents & Local-First Tools',
  description: 'Essays by AI engineer Ege Chelebi on agents and tools.',
  path: '/blog',
});

export default async function BlogIndexPage() {
  const posts = await getAllPosts();
  // Rendering logic...
}

```

### MDX Data Layer

The [`lib/blog.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog.ts) module handles file system operations to discover and parse MDX files stored in `app/(website)/blog/(post)/`. It computes reading times, extracts front matter, and exposes typed helper functions including `getAllPosts()`, `getPostMeta()`, and `getAllSlugs()`.

### SEO Configuration

Centralized SEO logic resides in [`lib/seo.ts`](https://github.com/woosal1337/blog/blob/main/lib/seo.ts), exporting constants like `SITE_URL` and `SITE_NAME` along with factory functions `createPageMetadata()`, `createPostMetadata()`, and `createEpisodeMetadata()`. These utilities return Next.js `Metadata` objects configured with canonical URLs, RSS alternates, and social media images.

## Implementing App Router File Structure

Next.js 14 App Router uses file-based routing where folders map directly to URL segments.

### Dynamic Route Segments for Blog Posts

Individual posts reside in `app/(website)/blog/(post)/[slug]/page.mdx`. The `(post)` route group organizes related files without affecting the URL structure, while `[slug]` creates dynamic segments for each blog post.

```tsx
// app/(website)/blog/(post)/[slug]/page.mdx
export const meta = {
  title: 'Understanding Context Engineering',
  date: '2024-01-15',
  tags: ['ai', 'llm'],
};

# Understanding Context Engineering

Article content here...

```

### Static Generation with generateStaticParams

The blog implements static generation by leveraging Next.js 14's automatic static parameter generation. The `getAllPosts()` function in [`lib/blog.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog.ts) provides the slugs needed for `generateStaticParams`, enabling the app to pre-render all posts at build time.

```tsx
// Implicit static generation via getAllPosts
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

```

## MDX Integration and Content Parsing

The repository uses `@next/mdx` alongside `next-mdx-remote` to compile MDX content at build time, eliminating client-side JavaScript overhead for blog content.

### Extracting Front Matter and Metadata

Each MDX file exports a `meta` object containing front matter data. The [`lib/blog.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog.ts) module dynamically imports these modules to retrieve metadata without loading full content into memory unnecessarily.

```tsx
// lib/blog.ts
export async function getAllPosts(): Promise<BlogPostMeta[]> {
  // Scans app/(website)/blog/(post)/* for page.mdx files
  const posts = await Promise.all(
    slugs.map(async (slug) => {
      const module = await import(`./(website)/blog/(post)/${slug}/page.mdx`);
      return { slug, ...module.meta };
    })
  );
  return posts.filter(p => !p.hidden);
}

```

### Calculating Reading Time

The data layer includes utilities to estimate reading time based on word count, attaching this data to the metadata object returned by `getAllPosts()`.

## SEO and Metadata Strategy

Comprehensive SEO implementation ensures proper indexing and social sharing across all blog routes.

### Page-Level Metadata Factories

The `createPageMetadata()` function in [`lib/seo.ts`](https://github.com/woosal1337/blog/blob/main/lib/seo.ts) constructs complete metadata objects including alternates for RSS feeds and canonical URLs.

```tsx
// lib/seo.ts
export function createPageMetadata({
  title,
  description,
  path,
}: PageMetadataOptions): Metadata {
  const canonical = absoluteUrl(path);
  const image = absoluteUrl('/og');
  
  return {
    title,
    description,
    alternates: { 
      canonical, 
      types: { 'application/rss+xml': absoluteUrl('/blog/rss.xml') } 
    },
    openGraph: {
      title,
      description,
      url: canonical,
      type: 'website',
      images: [{ url: image, width: 1200, height: 630, alt: title }],
    },
  };
}

```

### Open Graph and Twitter Cards

Individual post metadata extends the base configuration through `createPostMetadata()`, adding article-specific fields like `publishedTime`, `tags`, and author information for rich social media previews.

## Summary

- **App Router Structure**: Use route groups like `(website)` to organize layouts without URL prefixes, and place MDX files in `[slug]/page.mdx` for dynamic routing.
- **Data Layer Pattern**: Centralize file system operations in [`lib/blog.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog.ts) with typed functions like `getAllPosts()` to handle MDX parsing and metadata extraction.
- **Static Generation**: Implement `generateStaticParams` or rely on Next.js 14's automatic static optimization when using dynamic imports for MDX content.
- **SEO Abstraction**: Create factory functions in [`lib/seo.ts`](https://github.com/woosal1337/blog/blob/main/lib/seo.ts) to maintain consistent metadata across pages, handling Open Graph images, Twitter cards, and RSS alternates programmatically.
- **Type Safety**: Export `meta` objects from MDX files and type them with TypeScript interfaces to ensure compile-time validation of front matter.

## Frequently Asked Questions

### How does Next.js 14 App Router handle MDX files?

Next.js 14 treats MDX files as page components when placed within the App Router directory structure. By configuring `@next/mdx` in [`next.config.js`](https://github.com/woosal1337/blog/blob/main/next.config.js), the framework automatically processes these files at build time. The woosal1337/blog implementation stores posts in `app/(website)/blog/(post)/[slug]/page.mdx` and exports a `meta` object containing front matter data, which the data layer in [`lib/blog.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog.ts) imports to generate static pages and metadata.

### Where should I store my blog posts in a Next.js 14 App Router project?

Store blog posts within the App Router directory using a dynamic segment pattern like `app/(website)/blog/(post)/[slug]/page.mdx`. The `(post)` segment acts as a route group that doesn't appear in the URL but allows co-locating related files. This structure enables automatic routing where `/blog/my-post` maps to the MDX file at `[slug]/page.mdx` with `params.slug` equal to "my-post".

### How do I generate static params for dynamic blog routes?

Use the `generateStaticParams` async function in your dynamic route page component to return an array of slug objects. In the woosal1337/blog repository, [`lib/blog.ts`](https://github.com/woosal1337/blog/blob/main/lib/blog.ts) provides a `getAllSlugs()` helper that scans the file system, allowing the page to pre-render all posts at build time. Alternatively, when using MDX imports in a data layer, Next.js 14 can automatically statically optimize these routes if all data fetching occurs during the build process.

### What is the best way to handle SEO metadata in Next.js 14 App Router?

Create centralized factory functions in a dedicated file like [`lib/seo.ts`](https://github.com/woosal1337/blog/blob/main/lib/seo.ts) that return Next.js `Metadata` objects. Export constants for site-wide values (`SITE_URL`, `SITE_NAME`) and implement specific creators like `createPageMetadata()` for index pages and `createPostMetadata()` for individual articles. This approach keeps SEO logic consistent, handles Open Graph and Twitter card requirements, and leverages Next.js 14's native metadata API without cluttering individual page components.