Use Notion as a database for your Next.JS Blog

Use Notion as a database for your Next.JS Blog

Notion is an extremely powerful tool to manage your content by creating a database you can even add properties to pages: publication date, tags, etc.

In this Post you will learn how to fetch pages from the Notion API and render their content to create a wonderful Next.JS Blog entirely managed with Notion.

1. Create a Notion Database

A Notion Database is a list of pages with defined properties, it provides features to easily manage your content with different type of views (table, calendar, etc).

For the purpose of this guide we will add the following properties:

  • Title: The title of the post
  • Date: The date of the post
  • Status: The status of the post (Not started, Draft, Published)
  • Created time: The creation date of the post

Do not forget to create your posts and write some content in them!

Feel free to add your own properties and tweak them to your needs. You could for example add a publication date to automaticaly publish at a certain date.

Image description

Find the database ID

Later in this guide you will need the ID of your database. You will find it in the URL: https://www.notion.so/myworkspace/50b6156388e445eaaca3a3599d6f7ade

2. Get a Notion Token

Create an Integration

In order to interact with the Notion API you will need an Internal Integration Token aka. Notion Token.

Head over the following link to create a new Notion Integration. In our case we will only read data, you should only add the Read capacity.

When your integration is created you will have an Internal Integration Token. Save it and keep it safe, it will be the "Notion token" that you will use to authenticate to the API.

Image description

Authorize the integration to your databases

You must explicitly give the permission to your integration to query your databases.

Click on the ••• in the top right corner of your database, then on Add connection and select your Integration.

To avoid giving access to each of your databases, you can add the integration to a parent page.

3. Setup the project

Let's install the required dependencies. We are going to use four libraries:

$ yarn add @notionhq/client @notion-render/client @notion-render/hljs-plugin @notion-render/bookmark-plugin
# Or
$ npm install @notionhq/client @notion-render/client @notion-render/hljs-plugin @notion-render/bookmark-plugin

Then store your Internal Integration Token and the database Id into your .env.local file so you can access it later.

NOTION_TOKEN="secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
NOTION_DATABASE_ID="xxxxxxxxxxxxxxxxxxxxxxx"

4. Create the Post Page

Create the Notion Client

Create a new file lib/notion.ts we will add inside the functions we need to fetch our posts.

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!,
    filter: {
      property: "Status",
      select: {
        equals: "Published",
      },
    },
  });
});

export const fetchPageBySlug = React.cache((slug: string) => {
  return notion.databases
    .query({
      database_id: process.env.NOTION_DATABASE_ID!,
      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[]);
});

You can notice two things:

- import 'server-only';

This line make sure that the file never get imported by the client to avoid leaking your Notion Token.

- React.cache

Next.JS provide an extremely good caching system with the fetch() function but we can not benefit from it as we are using the Notion JS SDK.

Instead we can use React.cache, a powerful method that will returns the same result if we call our function with the same parameters.

Create the page

Create a page with a dynamic segment [slug]. Inside we will fetch our pages so it must be a Server Component:

// app/blog/[slug]/page.tsx
import { fetchPageBlocks, fetchPageBySlug } from "@/lib/notion";
import { notFound } from "next/navigation";

export default async function Page({ params }: { params: { slug: string } }) {
  const post = await fetchPageBySlug(params.slug);
  if (!post) notFound();

  const content = await fetchPageBlocks(post.id);

  return <></>;
}

Render the page content

import { fetchPageBlocks, fetchPageBySlug, notion } from "@/lib/notion";
import bookmarkPlugin from "@notion-render/bookmark-plugin";
import { NotionRenderer } from "@notion-render/client";
import hljsPlugin from "@notion-render/hljs-plugin";
import { notFound } from "next/navigation";

export default async function Page({ params }: { params: { slug: string } }) {
  const post = await fetchPageBySlug(params.slug);
  if (!post) notFound();

  const blocks = await fetchPageBlocks(post.id);

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

  renderer.use(hljsPlugin());
  renderer.use(bookmarkPlugin());

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

  return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
}

Next steps

  • Import your favorite highlight.js theme
  • Import the Notion theme from @notion-render/client/sass/theme.scss
  • Create a theme with your own branding
  • Use generateStaticParams to generate pages at build time
  • Use draftMode to preview your Post not published yet