Skip to main content

How to build a blog with Next.js and MDX

Written by
Pheak Minute
Published on
Aug 22, 2022
Views
--
How to build a blog with Next.js and MDX

Introduction

Building a blog with Next.js and MDX has become increasingly popular among developers who want to combine the power of React components with the simplicity of Markdown. In this tutorial, I'll walk you through creating a fully-functional blog from scratch.

What You'll Need

Here are the essential packages we'll be working with:

  • Next.js - The React framework for production
  • Content Collections - For managing and validating MDX content
  • Shiki - Syntax highlighting for code blocks
  • Rehype/Remark plugins - To enhance markdown processing

Why MDX for Blogging?

MDX allows you to write JSX directly in your markdown files, giving you the flexibility to:

  • Embed interactive React components within blog posts
  • Create reusable content blocks
  • Maintain type-safe frontmatter with validation
  • Generate static pages at build time for optimal performance

Setting Up Your Project

Let's start by creating a new Next.js project with TypeScript support:

Terminal
pnpm create next-app@latest my-blog --typescript --tailwind --app
cd my-blog

Installing Dependencies

We'll need a few additional packages to handle MDX content:

Terminal
pnpm add @content-collections/core @content-collections/mdx
pnpm add -D @content-collections/next

Project Structure

Here's the recommended folder structure for your blog:

  • app/ - Next.js App Router pages
  • content/blog/ - Your MDX blog posts
  • components/mdx/ - Custom MDX components
  • content-collections.ts - Content schema definition

Defining Your Content Schema

Create a content-collections.ts file in your project root to define the structure of your blog posts:

TypeScript
content-collections.ts
import { defineCollection, defineConfig } from '@content-collections/core'
import { compileMDX } from '@content-collections/mdx'

const posts = defineCollection({
  name: 'posts',
  directory: 'content/blog',
  include: '*.mdx',
  schema: (z) => ({
    title: z.string(),
    date: z.string(),
    summary: z.string(),
    modifiedTime: z.string().optional(),
  }),
  transform: async (document, context) => {
    const mdx = await compileMDX(context, document)
    
    return {
      ...document,
      slug: document._meta.path,
      content: mdx,
    }
  },
})

export default defineConfig({
  collections: [posts],
})

This configuration provides:

  • Type safety for your frontmatter
  • Automatic validation of blog post metadata
  • MDX compilation at build time
  • Slug generation from file paths

Creating Your First Blog Post

Now let's create a sample blog post. Create a new file at content/blog/hello-world.mdx:

MDX
content/blog/hello-world.mdx
---
title: Hello World - My First Blog Post
date: '2024-01-15T00:00:00Z'
summary: Welcome to my new blog built with Next.js and MDX!
---

## Welcome!

This is my first blog post using **Next.js** and **MDX**. 

### What I Can Do

With MDX, I can:

1. Write regular markdown
2. Use React components
3. Create interactive content

Pretty cool, right?

Building the Blog List Page

Create your blog listing page at app/blog/page.tsx:

TypeScript
app/blog/page.tsx
import { allPosts } from 'content-collections'
import Link from 'next/link'

export default function BlogPage() {
  // Sort posts by date (newest first)
  const sortedPosts = allPosts.sort((a, b) => 
    new Date(b.date).getTime() - new Date(a.date).getTime()
  )

  return (
    <div className='mx-auto max-w-4xl px-4 py-12'>
      <h1 className='mb-8 text-4xl font-bold'>Blog Posts</h1>
      
      <div className='space-y-6'>
        {sortedPosts.map((post) => (
          <article 
            key={post.slug}
            className='rounded-lg border p-6 transition-shadow hover:shadow-lg'
          >
            <Link href={`/blog/${post.slug}`}>
              <h2 className='mb-2 text-2xl font-semibold'>
                {post.title}
              </h2>
              <time className='text-sm text-gray-600'>
                {new Date(post.date).toLocaleDateString('en-US', {
                  year: 'numeric',
                  month: 'long',
                  day: 'numeric'
                })}
              </time>
              <p className='mt-3 text-gray-700'>{post.summary}</p>
            </Link>
          </article>
        ))}
      </div>
    </div>
  )
}

Creating the Blog Post Page

Now create the dynamic route for individual posts at app/blog/[slug]/page.tsx:

TypeScript
app/blog/[slug]/page.tsx
import { allPosts } from 'content-collections'
import { notFound } from 'next/navigation'
import { MDXContent } from '@content-collections/mdx/react'

export function generateStaticParams() {
  return allPosts.map((post) => ({
    slug: post.slug,
  }))
}

export default function BlogPostPage({ 
  params 
}: { 
  params: { slug: string } 
}) {
  const post = allPosts.find((p) => p.slug === params.slug)
  
  if (!post) {
    notFound()
  }

  return (
    <article className='mx-auto max-w-3xl px-4 py-12'>
      <header className='mb-8'>
        <h1 className='mb-4 text-4xl font-bold'>{post.title}</h1>
        <time className='text-gray-600'>
          {new Date(post.date).toLocaleDateString('en-US', {
            year: 'numeric',
            month: 'long',
            day: 'numeric'
          })}
        </time>
      </header>
      
      <div className='prose prose-lg max-w-none'>
        <MDXContent code={post.content} />
      </div>
    </article>
  )
}

Next Configuration

Update your next.config.js to enable Content Collections:

JavaScript
next.config.js
import { withContentCollections } from '@content-collections/next'

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Your Next.js config
}

export default withContentCollections(nextConfig)

That's It!

You now have a fully functional blog with:

✅ Type-safe content management
✅ MDX support for rich content
✅ Automatic routing
✅ Static generation for optimal performance

Additional Enhancements

Want to take your blog further? Consider adding:

  • Code syntax highlighting with Shiki or Prism
  • Reading time estimates
  • Table of contents generation
  • Related posts suggestions
  • RSS feed for subscribers
  • Search functionality
  • Tags and categories for organization

Useful Resources

Edit on GitHub
Last updated: Sep 16, 2025