---
title: "How to Add a Blog to Next.js: MDX, CMS, and Database Approaches Compared"
description: "A practical guide for SaaS founders and developers on adding a blog to Next.js using the App Router. Covers MDX, headless CMS, and database-driven approaches with folder structure, scaling advice, and SEO implementation details."
url: "https://rankbuddy.io/blog/how-to-add-a-blog-to-next-js-mdx-cms-and-database-approaches-compared"
published: "2026-05-09 15:43:40.904+00"
updated: "2026-05-10 11:38:24.475+00"
---

# 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?](#what-is-the-best-blog-setup-for-nextjs)
* [Approach 1: MDX Files in Git](#approach-1-mdx-files-in-git)
* [Approach 2: Headless CMS Integration](#approach-2-headless-cms-integration)
* [Approach 3: Database-Driven Blog](#approach-3-database-driven-blog)
* [Recommended Folder Structure for a Next.js Blog](#folder-structure-for-a-nextjs-blog)
* [SEO Essentials Every Next.js Blog Needs](#seo-essentials-for-a-nextjs-blog)
* [Scaling Your Next.js Blog for a SaaS Product](#scaling-your-nextjs-blog-for-saas)
* [Frequently Asked Questions](#faq-nextjs-blog)

***

## 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:**

1. Store posts in `/content/posts/` as `.mdx` files with frontmatter
2. Use `gray-matter` to parse frontmatter (title, date, slug, description)
3. Use `generateStaticParams()` to generate all post routes at build time
4. Use `generateMetadata()` in `/app/blog/[slug]/page.tsx` to set per-post metadata

A minimal `generateMetadata` export looks like this:

```ts
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](/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.tsx` for the index, `/app/blog/[slug]/page.tsx` for posts

A basic ISR fetch in a Server Component:

```ts
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.ts` and `/app/robots.ts`** are native to App Router. Next.js serves them automatically at `/sitemap.xml` and `/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`, and `openGraph` values 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.ts` and 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 the `alternates.canonical` field
* 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`, and `image` at minimum

**Images:**

* Use `next/image` for 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](/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](/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](/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

* [Next.js App Router Documentation — Metadata API](https://nextjs.org/docs/app/building-your-application/optimizing/metadata)
* [Next.js Incremental Static Regeneration](https://nextjs.org/docs/app/building-your-application/data-fetching/incremental-static-regeneration)
* [Google Search Central — Structured Data Guidelines](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data)
