How to Set Up a Next.js 14 App Router Blog with MDX and TypeScript
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 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 to configure default Open Graph and Twitter card properties.
// 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().
// 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 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, 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.
// 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 provides the slugs needed for generateStaticParams, enabling the app to pre-render all posts at build time.
// 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 module dynamically imports these modules to retrieve metadata without loading full content into memory unnecessarily.
// 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 constructs complete metadata objects including alternates for RSS feeds and canonical URLs.
// 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.mdxfor dynamic routing. - Data Layer Pattern: Centralize file system operations in
lib/blog.tswith typed functions likegetAllPosts()to handle MDX parsing and metadata extraction. - Static Generation: Implement
generateStaticParamsor rely on Next.js 14's automatic static optimization when using dynamic imports for MDX content. - SEO Abstraction: Create factory functions in
lib/seo.tsto maintain consistent metadata across pages, handling Open Graph images, Twitter cards, and RSS alternates programmatically. - Type Safety: Export
metaobjects 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, 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 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 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →