Use Notion as a database for your Next.JS Blog
Tags: webdev, nextjs, notion, tutorial
Published: 2023-05-13 16:31:41

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7jw1gfpzz3hysrzk264c.png)


### 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/<u>50b6156388e445eaaca3a3599d6f7ade</u>


## 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](https://www.notion.so/my-integrations) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ehe3we54ys3uzso2zhgb.gif)


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

- [@notionhq/client](https://www.npmjs.com/package/@notionhq/client) The official Notion Javascript SDK
- [@notion-render/client](https://www.npmjs.com/package/@notion-render/client) A library to transform Notion Blocks (page content) into HTML
- [@notion-render/hljs-plugin](https://www.npmjs.com/package/@notion-render/hljs-plugin) A plugin to highlight your code blocks
- [@notion-render/bookmark-plugin](https://www.npmjs.com/package/@notion-render/bookmark-plugin) A plugin to fetch website metadata to render bookmarks

```bash
$ 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.

```env
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.

```tsx
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:

```tsx
// 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

```tsx
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](https://highlightjs.org/) theme
- Import the Notion theme from `@notion-render/client/sass/theme.scss`
- Create a theme with your own branding
- Use [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) to generate pages at build time
- Use [`draftMode`](https://nextjs.org/docs/app/building-your-application/configuring/draft-mode) to preview your Post not published yet