How to Add a Blog to Next.js: MDX, CMS, and Database Approaches Compared
11 min read
How to Add a Blog to Next.js: MDX, CMS, and Database Approaches Compared
If you're building a Next.js blog for a SaaS product, the architecture decision you make in the first hour will follow you for years. There are three real options — MDX files in Git, a headless CMS, and a database-driven setup — and each one has a different breaking point. This guide compares all three, gives you a concrete folder structure, and covers the SEO implementation details that actually move rankings.
Table of Contents
- What Is the Best Blog Setup for Next.js?
- Approach 1: MDX Files in Git
- Approach 2: Headless CMS Integration
- Approach 3: Database-Driven Blog
- Recommended Folder Structure for a Next.js Blog
- SEO Essentials Every Next.js Blog Needs
- Scaling Your Next.js Blog for a SaaS Product
- Frequently Asked Questions
What Is the Best Blog Setup for Next.js?
The short answer: it depends on who's publishing and how often. A Next.js blog can be production-ready in under a day with any of the three approaches below. The question is which one you'll still be happy with at post 80.
Here's the decision frame:
- MDX in Git — best for solo developers publishing infrequently, who are comfortable with a PR-based workflow
- Headless CMS — best for SaaS teams where a marketer or founder needs to publish without touching code
- Database-driven — best when the blog is part of the product itself, not just a marketing channel
All examples in this guide use the App Router, which is the current standard. If you're still on Pages Router, the concepts transfer, but the file paths and API signatures differ.
Choosing the wrong architecture early is a real cost. Migrating 60 MDX files into a CMS mid-growth is a half-sprint of work that nobody wants to schedule. Pick correctly upfront.
Approach 1: MDX Files in Git
MDX lets you write Markdown with embedded React components. For technical blogs — documentation-style posts, code-heavy tutorials — it's genuinely useful. You get version control, no third-party dependency, and zero monthly cost.
How it works in App Router:
- Store posts in
/content/posts/as.mdxfiles with frontmatter - Use
gray-matterto parse frontmatter (title, date, slug, description) - Use
generateStaticParams()to generate all post routes at build time - Use
generateMetadata()in/app/blog/[slug]/page.tsxto set per-post metadata
A minimal generateMetadata export looks like this:
export async function generateMetadata({ params }) {
const post = getPostBySlug(params.slug);
return {
title: post.title,
description: post.description,
openGraph: { title: post.title, description: post.description },
};
}
Where MDX breaks down:
Every new post requires a Git commit, a PR, and a deploy. On a small team publishing once a month, that's fine. On a team publishing weekly, it adds up fast.
Field note: A SaaS founder shipping weekly posts via MDX will spend roughly 2–3 hours per week on Git overhead — branching, committing, waiting for deploys — before writing a single word of content. At 50+ posts, the repo itself becomes a maintenance burden: merge conflicts on the content directory, stale drafts in feature branches, and no non-developer can touch any of it.
MDX is a good starting point. It's not a good long-term system for most SaaS blogs. If you want to automate the content generation side of an MDX workflow, see Best AI Blog Writer for Next.js Websites in 2026.
Approach 2: Headless CMS Integration
A headless CMS — Contentful, Sanity, Hygraph — decouples your content from your codebase. Non-developers can publish, edit, and schedule posts without touching Git. For most SaaS marketing blogs, this is the right default.
How it works in App Router:
- Fetch posts server-side using
fetch()inside a Server Component - Use ISR (
revalidate) so Next.js rebuilds only changed pages rather than the full site - Route structure stays the same:
/app/blog/page.tsxfor the index,/app/blog/[slug]/page.tsxfor posts
A basic ISR fetch in a Server Component:
async function getPost(slug: string) {
const res = await fetch(`https://your-cms.io/posts/${slug}`, {
next: { revalidate: 3600 },
});
return res.json();
}
ISR matters for SEO. With 100+ posts, rebuilding the entire site on every publish is slow and unnecessary. ISR lets you serve cached static pages while revalidating only the updated content in the background. That keeps your Time to First Byte low — which Google measures.
Metadata from the CMS:
Your generateMetadata() function pulls title, description, and Open Graph data directly from the CMS record. This means your marketing team can update SEO metadata without a code deploy.
Trade-offs to know upfront:
- Adds a paid third-party dependency (most CMS plans are $0–$99/month at early scale)
- Requires a content modeling step before you can publish anything
- Vendor lock-in is real — switching CMS later means migrating your content schema
Best fit: SaaS teams where a marketer or founder needs to publish independently. If that's not your situation, MDX is simpler.
Approach 3: Database-Driven Blog
Store posts in Postgres (or PlanetScale) and query them with Prisma or Drizzle inside Server Components. This gives you full control: drafts, scheduling, author roles, tagging, analytics — all in one system you own.
When this makes sense:
- The blog is part of the product (e.g., user-generated content, multi-tenant blogs where each customer has their own blog)
- You need custom publishing workflows that no CMS supports out of the box
- You're already running a Postgres database and don't want another vendor
Route structure is identical to the other approaches — /app/blog/[slug]/page.tsx — the data source changes, not the routing.
SSR vs. ISR for database-driven blogs:
SSR (Server-Side Rendering) hits your database on every request. That adds latency and database load. ISR is usually the better default — cache the rendered page and revalidate on a schedule or on-demand when a post is updated.
You can generate canonical tags and JSON-LD structured data dynamically from the database record, which keeps your SEO metadata in sync with your content without any manual steps.
Honest assessment: For most early-stage SaaS blogs, this approach is overkill. You're adding database migrations, schema design, and backend code to a problem that a CMS solves in an afternoon. Build this when you genuinely need the control, not because it feels more engineered.
Recommended Folder Structure for a Next.js Blog
Consistency matters more than perfection here. Pick a structure and document it.
/app
/blog
page.tsx ← Blog index (list of posts)
/[slug]
page.tsx ← Individual post page
sitemap.ts ← Dynamic sitemap (served at /sitemap.xml)
robots.ts ← Robots rules (served at /robots.txt)
/components
/blog
PostCard.tsx ← Used on the index page
PostHeader.tsx ← Title, date, author on post pages
TableOfContents.tsx ← Optional, for long posts
/lib
blog.ts ← All fetch, filter, and sort logic lives here
/content
/posts ← MDX files only; remove this for CMS or DB approaches
Key decisions in this structure:
- Keep data logic in
/lib/blog.ts, not in page files. Page files should be thin — they call functions and render components, nothing else. /app/sitemap.tsand/app/robots.tsare native to App Router. Next.js serves them automatically at/sitemap.xmland/robots.txt. No plugin needed.- For CMS or database approaches,
/content/posts/disappears entirely and is replaced by API or database calls inside/lib/blog.ts.
This structure scales cleanly from 10 posts to 500. The folder shape doesn't change — only the data source does.
SEO Essentials Every Next.js Blog Needs
Next.js gives you the tools. Most teams use about 60% of them. Here's the full checklist:
Metadata per post:
- Export
generateMetadata()from every/app/blog/[slug]/page.tsx - Return
title,description, andopenGraphvalues pulled from your content source - Never use the same description for two posts — duplicate metadata is a real ranking signal problem
Sitemap:
- Add
/app/sitemap.tsand export a function that fetches all published post slugs - Return an array of
{ url, lastModified }objects - Next.js handles the rest — no sitemap plugin required
Canonical URLs:
- Next.js does not auto-generate canonical tags
- Set them explicitly in
generateMetadata()using thealternates.canonicalfield - Missing canonicals on a blog with paginated or tagged archives will cause duplicate content issues
Structured data (JSON-LD):
- Render a
<script type="application/ld+json">tag inside a Server Component with your Article schema - No external library needed — a plain object serialized with
JSON.stringify()works fine - Include
headline,datePublished,dateModified,author, andimageat minimum
Images:
- Use
next/imagefor every post image — lazy loading, modern formats (WebP/AVIF), and size optimization are automatic - A blog page scoring below 80 on Core Web Vitals will struggle to rank regardless of content quality
Internal linking:
- Link between related posts deliberately — this is one of the highest-ROI SEO actions and is almost always skipped
- At scale, manual internal linking becomes impossible; see How to Set Up Blog Automation on Next.js for SaaS and Save 20+ Hours Weekly for how to automate it
Scaling Your Next.js Blog for a SaaS Product
At 10 posts, any approach works. At 100 posts, your content workflow becomes the bottleneck — not your code.
What breaks first:
- Manual metadata entry per post (someone forgets the description, Open Graph image is missing)
- Internal linking — nobody goes back to update old posts when a new one is published
- Slug and URL consistency — posts get published with inconsistent naming conventions
ISR configuration for blog pages:
Use a revalidate window of 60–3600 seconds depending on how often posts change. A marketing blog that publishes twice a week doesn't need a 60-second revalidation window — 3600 is fine and reduces unnecessary rebuilds. Avoid full SSR for blog pages unless you have a specific reason (e.g., personalized content per user).
Tag and category pages:
Add /app/blog/tag/[tag]/page.tsx and /app/blog/category/[category]/page.tsx. These pages add significant crawlable surface area for long-tail keywords and give Google more entry points into your content. Most SaaS blogs skip this and leave traffic on the table.
Track conversions, not just traffic:
Most SaaS blogs have 3–5 posts that drive 80% of their signups. Identify those early and invest in keeping them updated, well-linked, and technically clean. Chasing traffic volume without watching conversion attribution is how you end up with 200 posts and a flat MRR chart.
Field note: A B2B SaaS team that moved from a manual MDX workflow to a CMS-backed blog with automated metadata cut their publishing time from roughly 4 hours to under 45 minutes per post. The time savings came almost entirely from eliminating the Git overhead and manual SEO field entry — not from writing faster.
If your team is publishing more than 4 posts per month, the manual overhead of metadata, internal linking, and sitemap management adds up to a real engineering cost. A purpose-built automation layer is worth evaluating at that point. See How to Set Up Blog Automation on Next.js for SaaS and Save 20+ Hours Weekly for a practical breakdown, or Best AI Blog Writer for Next.js Websites in 2026 if you're evaluating tools to scale content production without scaling headcount.
Frequently Asked Questions: Next.js Blog Setup
Is Next.js good for SEO?
Yes. App Router with Server Components, ISR, and native metadata APIs gives you full control over every SEO signal Google measures — metadata, structured data, sitemaps, canonicals, and Core Web Vitals. The framework doesn't limit you; the implementation does.
Should I use MDX or a CMS for my Next.js blog?
MDX is fine for developer-only teams publishing infrequently. Use a headless CMS if anyone other than a developer needs to publish, or if you plan to publish more than twice a month. The Git overhead of MDX compounds quickly.
Does SSR improve SEO in Next.js?
SSR ensures content is rendered server-side and fully crawlable, but ISR is usually the better default for blog pages. ISR combines static performance with fresh content — you get fast TTFB without serving stale posts indefinitely.
How do I add metadata in Next.js App Router?
Export a generateMetadata() function from your page file. It can be async, so you can fetch post data and return dynamic title, description, and openGraph values. This replaces the <Head> component from Pages Router.
How do I create a sitemap in Next.js?
Add /app/sitemap.ts and export a default function that returns an array of URL objects with url and lastModified fields. Next.js serves it automatically at /sitemap.xml — no plugin or configuration needed.
How do I add structured data to a Next.js blog?
Render a <script type="application/ld+json"> tag inside a Server Component with your Article schema serialized as JSON. No external library is required. Include headline, datePublished, dateModified, author, and image at minimum.
What is the best folder structure for a Next.js blog?
Use /app/blog/page.tsx for the index, /app/blog/[slug]/page.tsx for posts, /lib/blog.ts for all data logic, and /components/blog/ for shared UI components. Add /app/sitemap.ts and /app/robots.ts at the app root.
How do I optimize a Next.js blog for Google?
Cover the non-negotiables: dynamic metadata per post, a generated sitemap, explicit canonical URLs, JSON-LD Article schema, next/image for all images, and deliberate internal linking between posts. Get Core Web Vitals above 80 before worrying about anything else.
Sources
Continue reading
Related Reading
Hand-picked posts that pair well with what you just read.
From Git-Based MDX to Automated SEO Workflows: Using RankBuddy to Scale a Next.js Content Engine
A practical guide for SaaS founders and growth teams on replacing manual MDX blog management with automated SEO workflows in Next.js, using RankBuddy to scale content operations, improve rankings, and drive organic signups.
Read11 minNext.js Blog Performance and SEO: ISR, Caching, Core Web Vitals, and Indexing Tradeoffs
A practical guide for SaaS founders and developers on optimizing Next.js blog performance for SEO—covering ISR vs SSR tradeoffs, caching strategies, Core Web Vitals, and indexing pitfalls in the App Router.
Read10 min