Next JS Project: How to use Notion as a CMS

I use Markdown to store the static content for my NextJS websites. There are many reasons why: it’s free, it’s open, it’s portable, it’s editable, and there is no lock-in.

However, on my personal blog, I now have about 75 blog articles in both French and English. And it is becoming difficult to get that content correctly organised. I’ve explained previously how I ensure that the data stored in the FrontMatter follows the right shape using an SDK called ContentLayer.

But is there a way to better organise it?

I want something trivial to set up. I want to be able to edit content on my phone when I’m commuting to work. I’ve tried different solutions, but I’ve settled on using Notion to track all my content.

If you don’t know Notion, it’s a free tool for organising your life. And it allows you to easily set up tables with custom fields. And that’s great for my content because each type of content has different fields. For example, I have a table for blog posts, one for pages, and one for newsletter issues (incidentally, I’ve provided a link in the description if you want to sign up).

The reason why Notion, Markdown and ContentLayer are a perfect fit for my use cases and my situation is because all three allow you to define your data schema in the content itself.

So, what happens when I want to update content? I run a script that downloads data from Notion and saves it as Markdown. I’ll explain how in a second.

So, how do we go about it? Well, allow me to walk you through the steps.

We’ll use the example of portfolio content because it’s simple. As I live in Paris, France, I’ll use photos I’ve taken in Paris as content. Let’s dive in.

Creating the Notion Database(s)

Let’s start by setting up the content. In Notion, I’ll create a page called “Website”, and within that page, I’ll create a page called BlogPosts and one called Portfolio.

Now, inside Portfolio, let’s set up a table that will hold our content. We start by defining the fields the table will hold. For convenience, I’ll set all the field names to follow JavaScript variable naming conventions. We already have a Title and Tags, but let’s edit their name down to lowercase.

Now let’s add :

  • a date, where we’ll store when the photo was taken, and we’ll call that “date”
  • we’ll also add a text field called “location” to state where this was taken
  • I also like to have a checkbox field called “enabled” that allows me to indicate when my content is ready (or not)
  • finally, let’s add an image and media field I’ll call “image”. That should do for starters, and we can always add more later.

Now, we need to add content to the table so that it returns something when we fetch it…

Set up a Notion integration

Once that is done, the next step is to create a Notion integration. There are various ways to get to where you do so. The easiest way is to head to the notion website, sign in and go to: `[/my-integrations]

You can also get there by clicking on the three dots at the top right of a page. As we’ll be using this menu quite a bit, let’s call it the “Page Menu”.

Now scroll down to “Add Connections” and again to “Manage Connections”. Then, at the bottom of the page, you have a link that says “Develop or manage integration”.

Once here, we need to click on “Create New Integration”. Your integration will be internal and linked to your workspace. You need to give your integration a name. For example, Notion2Next. You can also add an image if you’re so inclined, but it’s not required here. Once you’re done, click on submit.

Now we have the information for the integration. We need two things. First, on the “Capabilities” tab, check that your integration has the rights to Read and Update content.

Next, head over to the “Secrets” tab and copy the secret. Before we go any further, let’s head to the NextJS project.

I have my secrets stored in a .envfile. We don’t need the notion secret in production, so the .envis the perfect place to store it. So let’s create a line in the file called NOTION_SECRET=and copy-paste the secret in there

Before we do any more work on the NextJS project, we need to share our content with the integration.

We head to the master page we’ve created that references our different databases and open the “Page Menu” again. Then we head down to “Add Connexions”. Now, we need to select the connection we created.

While we are here, let’s get the database id. Let’s open the page that holds our portfolio content. Again, let’s open the “Page Menu”, but this time click on “Copy link”.

Now, let’s head back to the NextJS project and open up the .envfile again. Let’s type NOTION_PORTFOLIO_DATABASE_ID= and paste in the link we just copied. It should look something like this :

As you can see, there are four parts to the link. The first part is the Notion website URL ( We can delete that. The second part is your user name (in my case, “kodaps”) followed by a slash (/). We can delete that too.

We’re left with a long hexadecimal string and then a ?v= followed by more characters.

We only want to keep the first hexadecimal string part. We delete everything after the question mark (including the question mark itself). And what’s left is the ID of the database that holds your content. Yay :)

NextJS setup

Now, retrieving data from Notion and saving it involves quite a bit of code. However, to save time for everyone, I’ve packaged that code into an NPM library.

The first thing to do is add the @kodaps/notion-parse and dotenv packages to your dev dependencies using your package manager, for example:

pnpm add @kodaps/notion-parse dotenv -D

Now, notion-parse is still a young library, and it supposes a number of things about the layout of your folders and doesn’t have much documentation since I cooked the thing up over the weekend.

However, it’s really simple to use.

I’ve created a script/folder in my NextJS repository and created a script called notion.js. This script will use the environment variables and the library to fetch the content.

The package exposes a parseNotion function that takes three parameters.

  • The first is the notion secret we fetched earlier
  • The second is the folder where we’ll be putting the markdown content
  • The third is an array of objects.

Each object needs to specify the notion database ID (using the databaseId field) and the content type (using the contentType field). The object can also specify which fields to ignore from the Notion database by specifying filterFields.

And if you have a field in your database that specifies the content language, you can pass the name of that field in using languageField.

So here, for example, the Portfolio content is managed as is, and the Post content will be stored in different directories based on language, with some fields being ignored.

const NotionParse = require('@kodaps/notion-parse');
const dotenv = require('dotenv');

const go = async () => {

	if (process.env.NOTION_SECRET) {
		await NotionParse.parseNotion(process.env.NOTION_TOKEN, 
				databaseId: process.env.NOTION_PORTFOLIO_DATABASE_ID || '',
				contentType: 'Portfolio'
				databaseId: process.env.NOTION_POST_DATABASE_ID || '',
				contentType: 'Post',
				languageField: 'lang',
				filterFields: [ 'translation', 'createdAt', 'status']


go().then(() => {

For the moment, the code supposed several things :

  1. that the files are stored in a subfolder of the folder passed in as a parameter (here ./src/content) based on the content type
  2. that the ContentLayer type names map to the subfolders. So, for instance, for the Post content type, the files will be stored in ./src/content/post
  3. that the Notion token and database IDs are stored in environment variables and that there is one database per content type
  4. That the title of the content is stored in a 'title' field in Notion

If you’re interested, I’ve shared the repository for the NPM package in the description so you can see how the code fetches the content from Notion. When I do a video explaining how I created the package I’ll post it here.

In the mean time, the next step is to bring all this content together to make the portfolio page, and that will be the next video, you can find it here as soon as it is ready.

So I’ll see you in the next video.

Made by kodaps · All rights reserved.
© 2023