Integrate Sanity, Contentful, or Markdown (MDX) with Next.js App Router
Modern web apps often need dynamic, editable content that’s not hardcoded. In this module, you’ll learn how to:
- Connect a Headless CMS (e.g., Sanity, Contentful, Strapi)
- Fetch content using REST or GraphQL APIs
- Render blog-style posts using MDX (Markdown + JSX)
- Compare CMS vs MDX and when to use each
🧱 Common Use Cases for CMS in Next.js
- Marketing sites – update landing page content without code
- Blogs – easily manage posts, authors, tags
- Portfolios – update projects dynamically
- Docs – power help sections with markdown or rich text
✅ Option 1: Integrate Sanity.io (or Contentful)
Step 1: Create a Free CMS Project
Visit:
Set up a new project with a “Blog” schema or use a starter template.
Step 2: Install Client SDK
Sanity:
npm install @sanity/client
Step 3: Set Up Sanity Client
// src/lib/sanity.ts
import { createClient } from '@sanity/client';
export const sanity = createClient({
projectId: 'your_project_id',
dataset: 'production',
apiVersion: '2023-01-01',
useCdn: true,
});
Step 4: Fetch CMS Data in Server Component
// src/app/blog/page.tsx
import { sanity } from '@/lib/sanity';
type Post = {
_id: string;
title: string;
slug: { current: string };
};
export default async function BlogPage() {
const posts: Post[] = await sanity.fetch(`*[_type == "post"]`);
return (
<ul className="space-y-4 p-4">
{posts.map((post) => (
<li key={post._id}>
<a href={`/blog/${post.slug.current}`} className="text-blue-600 underline">
{post.title}
</a>
</li>
))}
</ul>
);
}
✅ Contentful or Strapi would work similarly using GraphQL/REST.
✅ Option 2: Use MDX (Markdown + JSX)
Great for developers and static content blogs.
Step 1: Install MDX Support
npm install next-mdx-remote gray-matter remark remark-html
Step 2: Create Blog Posts in Markdown
/content/posts/first-post.mdx
---
title: "First Post"
date: "2025-05-01"
---
# Hello World
This is a **Markdown + JSX** post.
<MyCustomComponent />
Step 3: Parse and Render in Server Component
// src/app/blog/[slug]/page.tsx
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { MDXRemote } from 'next-mdx-remote/rsc';
const POSTS_DIR = path.join(process.cwd(), 'content/posts');
export default async function BlogPost({ params }: { params: { slug: string } }) {
const filePath = path.join(POSTS_DIR, `${params.slug}.mdx`);
const raw = fs.readFileSync(filePath, 'utf-8');
const { content, data } = matter(raw);
return (
<article className="p-8 max-w-2xl mx-auto prose">
<h1>{data.title}</h1>
<MDXRemote source={content} />
</article>
);
}
✅ CMS vs MDX – Which Should You Use?
Use Case | Choose This |
---|---|
Marketing site / editors | Headless CMS |
Dev blog / docs | MDX |
Dynamic user-generated | CMS + DB |
Custom interactivity | MDX with components |
✅ Summary
Feature | Tool / Example |
CMS Integration | Sanity, Contentful, Strapi |
Markdown Content | next-mdx-remote + gray-matter |
Render MDX with JSX | <MDXRemote source={content} /> |
Best for blogs/docs | MDX with static generation |
Best for live editors | Sanity/Contentful (REST or GraphQL) |
🔜 Coming Up Next:
In Module 12, you’ll build a complete real-world project — an E-Commerce app or Developer Portfolio — using everything learned so far, including auth, CRUD, global state, and deployment.
Would you like this module also converted into Markdown blog format?