Introduction

I want to use Notion as a content management system (CMS) for my websites built with Next.js 15 using the App Router. When I initially searched on GitHub, I only found two main solutions: react-notion and its successor react-notion-x. Unfortunately, I couldn’t successfully configure either of these in my project.

After further research, I discovered a much better approach by combining two different libraries: notion-to-md and markdown-to-jsx. This combination allowed me to build a lightweight and easy-to-configure CMS solution for Next.js 15 apps.

This approach works by first converting Notion content to Markdown format using notion-to-md, then rendering that Markdown as React components using markdown-to-jsx. I believe this is the most effective solution for using Notion as a CMS with Next.js in 2025.

Why Notion?

Notion offers key advantages as a CMS for Next.js projects: it’s already part of my content workflow, making it a natural CMS choice; its familiar interface eliminates learning curves; built-in collaboration simplifies team editing; it creates a direct content pipeline to my website; and rich text editing and AI assistance improve content quality.

Using Notion as my CMS maintains a single source of truth, eliminating reformatting between platforms. This streamlines publishing and lets me focus on creating quality content instead of technical details.

Why Next.js?

I chose Next.js for its accessibility to non-frontend experts and solid React foundation. Its popularity ensures abundant documentation and community support when problems arise.

The framework’s server-side rendering improves both performance and SEO for content-heavy sites, making it perfect for a Notion-based CMS. This pairing lets me deploy robust websites efficiently without frontend complexity.

Step by Step to Set Up Notion as a Headless CMS for Next.js 15

1. Set Up Notion Integration

First, you need to create a Notion integration to access your content programmatically:

  1. Go to Notion’s integration page and create a new integration
  2. Give your integration a name and select the workspace where your content lives
  3. Copy the secret token — you’ll need this for your Next.js application
  4. Share your Notion pages with the integration to grant access to your content

2. Install Required Dependencies

In your Next.js 15 project, install the necessary packages:

plain text
npm install @notionhq/client notion-to-md markdown-to-jsx

If you use pnpm, run:

plain text
pnpm add @notionhq/client notion-to-md markdown-to-jsx

3. Configure Notion Client and Content Retrieval

Create a utility file to set up your Notion client and add function to fetch content:

typescript
// lib/notion.ts
import { Client } from "@notionhq/client";
import { NotionToMarkdown } from "notion-to-md";

// Initialize Notion client
const notion = new Client({
  auth: process.env.NOTION_API_KEY,
});
// Create the notion-to-md converter
const n2m = new NotionToMarkdown({ notionClient: notion });
/**
 * Retrieves a Notion page and converts it to markdown format
 * @param id The Notion page ID to retrieve
 * @returns A promise that resolves to the markdown string representation of the page
 */
export async function getPageMarkdown(id: string): Promise<string> {
  const mdblocks = await n2m.pageToMarkdown(id);
  const markdown = n2m.toMarkdownString(mdblocks)["parent"];
  return markdown;
}

4. Getting the Notion Page ID

To use a Notion page with your Next.js application, you need to obtain its unique ID. Here’s how to find it:

  1. Open the Notion page you want to use in your browser
  2. Look at the URL in your address bar. It will look something like: https://www.notion.so/username/Page-Title-ID
  3. The ID is the string of characters at the end of the URL after the page title
  4. In the example URL https://www.notion.so/ppaanngggg/Notion-as-a-Headless-CMS-for-Next-js-15-2025-287b7de6b453802a9af4faf6a5cbd28e, the page ID is 287b7de6b453802a9af4faf6a5cbd28e
Sometimes, the ID might include hyphens that separate parts of the ID. When using the ID with the Notion API, you only need the ID part without any hyphens.

You’ll use this ID when calling the getPageMarkdown() function we created earlier.

5. Render Markdown Content in Next.js

I selected markdown-to-jsx for rendering the markdown output from the getPageMarkdown function due to its lightweight nature and robust server-side rendering support. Below is a simple implementation example in a page.tsxfile:

typescript
import { findBlogByUri, getBlogMarkdown } from "@/lib/notion";
import Markdown from "markdown-to-jsx";

export default async function BlogPage({
  params,
}: {
  params: Promise<{ id: string }>,
}) {
  const { id } = await params;

  // Fetch and convert Notion page content to markdown
  // Uses the utility function we created earlier
  const mdString = await getPageMarkdown(id);
  return (
    <main>
      {/* Render markdown content as React components
          markdown-to-jsx handles conversion of markdown syntax to HTML */}
      <Markdown>{mdString}</Markdown>
    </main>
  );
}

At this point, we’ve successfully rendered our Notion page in Next.js. However, you might notice that the markdown content lacks any formatting. Let’s address this issue.

You can use Next.js’s unstable_cache function to cache the content, which significantly improves loading speed.

6. Style Your Markdown Content

For styling the markdown content, I used the light theme from github-markdown-css. After downloading the CSS file and saving it to styles/markdown.css, I updated the page.tsx file as shown below:

typescript
import { findBlogByUri, getBlogMarkdown } from "@/lib/notion";
import Markdown from "markdown-to-jsx";
import "@/styles/markdown.css"; // load css file

export default async function BlogPage({
  params,
}: {
  params: Promise<{ id: string }>,
}) {
  const { id } = await params;
  const mdString = await getPageMarkdown(id);
  return (
    <main>
      {/* set class name of css */}
      <Markdown className="markdown-body">{mdString}</Markdown>
    </main>
  );
}
In fact, I customized the original CSS to better match my preferences. You can find my version at https://gist.github.com/ppaanngggg/e20021e3ce1fb777d9174e30df59e742

Conclusion

In this guide, I’ve detailed how to create a lightweight, efficient headless CMS using Notion as the content source for Next.js 15 applications. By combining notion-to-md for converting Notion content to markdown and markdown-to-jsx for rendering that content in React, we’ve established a straightforward content pipeline that leverages the strengths of both platforms.

As we move further into 2025, this approach remains relevant and effective for leveraging Notion’s powerful collaboration features alongside Next.js’s performance benefits. By following the steps outlined in this guide, you can create a seamless content workflow that simplifies the publishing process while maintaining full control over your website’s presentation.