Notion as a Dynamic CMS Backend for Next.js: Real-Time Updates


This guide demonstrates how to use Notion as a dynamic CMS backend for your Next.js app. With this integration, any changes made in Notion will instantly update your app, enabling seamless real-time synchronization without the need for redeployment.


requirements:

Dependencies
npm i @notionhq/client 
npm i @notion-render/client
npm i @notion-render/hljs-plugin
Create a file named notion.ts in a new folder called lib
//notion.ts content

import "server-only";

import { Client } from "@notionhq/client";
import React from "react";
import {
  BlockObjectResponse,
  PageObjectResponse,
} from "@notionhq/client/build/src/api-endpoints";

export const notion = new Client({
  auth: process.env.NOTION_TOKEN,
});

export const fetchPages = React.cache(() => {
  return notion.databases.query({
    database_id: process.env.NOTION_DATABASE_ID_ARTICLES!,
    filter: {
      property: "Status",
      status: {
        equals: "Completed",
      },
    },
  });
});

export const fetchPageBySlug = React.cache((slug: string) => {
  return notion.databases
    .query({
      database_id: process.env.NOTION_DATABASE_ID_ARTICLES!,
      filter: {
        property: "Slug",
        rich_text: {
          equals: slug,
        },
      },
    })
    .then((res) => res.results[0] as PageObjectResponse | undefined);
});

export const fetchPageBlocks = React.cache((pageId: string) => {
  return notion.blocks.children
    .list({ block_id: pageId })
    .then((res) => res.results as BlockObjectResponse[]);
});
Creact notion Token
  1. Go to Notion Developers and sign in with your Notion account.
  2. Click on "New integration" to create a new integration.
  3. Fill in the required details, such as the name of your integration, and select the workspace it will be associated with. (Make sure to select just 'Read content' to not make anyone edit your Notion database.)

Creat notion page

After entering Notion, create a new page and start a database by typing /Database-inline or /Gallery view .

Open the page as a full page and get the page ID from the link.

Here is an example of a Notion page link, with the page ID highlighted in red:

https://www.notion.so/7707aa0a9b2644429e40635f8b14cca6?v=d0bcbb1785af4ec59dadc0b21fe135be

Add two properties: Slug and Status.

Status:

Slug:

Note: Preserve the original case (uppercase or lowercase) when naming properties.

Finally, connect your page using your Notion token.

Next.JS

Environment variables

Create a .env file and insert your Notion token and Notion page ID

NOTION_TOKEN="********************************************************"
NOTION_DATABASE_ID="********************************"
Fetch posts to your desired page
import React from "react";
import { fetchPages } from "@/lib/notion";

const Page = async () => {
  const posts = await fetchPages();
  
  return (
	  <div>Page</div>
  )

};

export default Page;
Posts handle

The posts.results array contains all the data, so using the map function is essential to display it.

{posts.results.map((post: any) => {
//Title
<h2>{post.properties.Title.title[0].plain_text}</h2>
//Other properties, such as date or description, can also be added
//Date
<h3>{post.properties.Date.date.start}</h3>
//Description
<p>{post.properties.Description.rich_text[0].plain_text}</p>
})

In general, you can add any properties by adhering to the schema of the desired properties.

//Exaple : Logging posts.resutls[0] to the console.
{
  object: 'page',
  id: '73abc49c-8608-4a68-9348-a5b662ff8f34',
  created_time: '2024-05-13T13:47:00.000Z',
  last_edited_time: '2024-08-17T11:13:00.000Z',
  created_by: { object: 'user', id: '0127936b-3791-47a7-ba36-063587d039b2' },
  last_edited_by: { object: 'user', id: '0127936b-3791-47a7-ba36-063587d039b2' },
  cover: null,
  icon: null,
  parent: {
    type: 'database_id',
    database_id: '7edf7558-91ad-432e-b868-6ef469cb3671'
  },
  archived: false,
  in_trash: false,
  //Next, we'll target the specific propertie: posts.resutls[0].properties
  properties: {
    Date: { id: '%3FqpY', type: 'date', date: [Object] },
    Tags: { id: 'ZG%5EE', type: 'multi_select', multi_select: [Array] },
    Link: {
      id: '%5B_%5Cf',
      type: 'url',
      url: 'https://brahmihoussem.github.io/react-P_2_-chatMenu/'
    },
    Status: { id: 'elCU', type: 'status', status: [Object] },
    Description: { id: 'u%3A%5Eb', type: 'rich_text', rich_text: [Array] },
    //Next, we'll target the Title: posts.resutls[0].properties.Title.title[0].plain_text
    Title: { id: 'title', type: 'title', title: 
			    [
			    {
				    type: 'text',
				    text: { content: 'Chat app', link: null },
						    annotations: {
						      bold: false,
						      italic: false,
						      strikethrough: false,
						      underline: false,
						      code: false,
						      color: 'default'
						    },
						    plain_text: 'Chat app',
						    href: null
				  }
				  ] 
	  }
  },
  url: 'https://www.notion.so/Chat-app-73abc49c86084a689348a5b662ff8f34',
  public_url: 'https://tungsten-liquid-d75.notion.site/Chat-app-73abc49c86084a689348a5b662ff8f34'
}

Slug

To handle the slug, paste this code into the [slug] directory.

import { fetchPageBlocks, fetchPageBySlug, notion } from "@/lib/notion";
import React from "react";
import { NotionRenderer } from "@notion-render/client";
import hljsPlugin from "@notion-render/hljs-plugin";

const Page = async ({ params }: { params: { slug: string } }) => {
  const post = await fetchPageBySlug(params.slug);
  if (!post) {
    return <div>Post not found</div>;
  }
  const blocks = await fetchPageBlocks(post.id);

  const renderer = new NotionRenderer({
    client: notion,
  });

  renderer.use(hljsPlugin({}));

  const html = await renderer.render(...blocks);

  return (
    <>
      <div
        className="mx-auto "
        dangerouslySetInnerHTML={{ __html: html }}
      ></div>
    </>
  );
};

export default Page;

Finally, for styling, use the inspector tool to identify the desired class and then edit and style it in global.css.