Skip to content

Using Notion as a CMS

Unlike most note-taking applications, Notion features a full suite of notation-like tools, from tools to track tasks to ones that help with journaling, all in one place. If you're an experienced user, you may have used some of their advanced concepts like tables as databases, custom templates, and table functions.

As of May 13, 2021, Notion released a beta version of their API client, opening up another range of possibilities for the app. Users of the client can query databases, create pages, update blocks, perform searches, and much more.

We'll look at how easy it is to get started with the client today.

Note: For this simple demonstration, we won't use authentication, but it's highly recommended no matter what type of app you decide to create.

🔗Project Setup

We have a few options for building a scalable blog site. For this stack, we'll be using NextJS because it's relatively lightweight compared to most frameworks, and has several readily available features for a standard React app. But Remix works just as well.

In a folder of your choice, create the NextJS app with:

npx create-next-app@latest

or

yarn create next-app

Note: add --typescript at the end to generate a TypeScript project.

I'm using the TypeScript project for this demo.

NextJS Welcome

If you're unfamiliar with NextJS, I recommend their Getting Started Guide. In this new project folder, we have all the necessary things to run the app.

Install the necessary dependencies:

npm install -S @notionhq/client

🔗Create the Notion Database

For this next step and the next, you'll need a Notion account to create databases, and an integration key.

Now, create a table for your blog posts in Notion. This will become the database for referencing posts for the site.

Notion Table Creation

I'm making this generic developer blog database, but feel free to make your database specific to a target audience or subjects.

Notion Create Database Example

🔗Notion Setup and Integration

Before using Notion's API to query any data, we need access via an integration key. Head over to the Notion's authorization guide and follow the instructions to create your key. I'm using the most basic setup for reading from my database.

Notion integration Key Setup

Continue with the integration key guide up to Step 2 which references how to grant the intergration key rights to the database.

With the integration key and database ID handy, let's configure the app to use them.

🔗Querying the database

In your favourite code editor, create an .env.local file with the following:

NOTION_API_KEY=secret_xxxxxxxxxxxxxxxxxxxxxx
NOTION_DATABASE_ID=xxxxxxxxxxxxxxxxxxxxxx

Note: it won't matter if you wrap your values in quotations.

NextJS comes with the ability to read local environment variables. It also ignores versions of this file in its .gitignore so we don't have to. If publishing an app to production, it's recommended to use environment variables.

Next, create a src/api/client.ts file at the root of the project. This will contain an easily referenced version of our client for querying Notion.

import { Client } from '@notionhq/client';

const NOTION_API_KEY = process.env.NOTION_API_KEY ?? '';

export const notion = new Client({ auth: NOTION_API_KEY });

Follow that up with a src/api/query-database.ts file:

import { notion } from './client';

export const queryDatabase = async () =>
  await notion.databases.query({
    database_id: process.env.NOTION_DATABASE_ID ?? '',
  });

🔗Parse the Query

Because the returned query object is so complex, we need to parse it. There are better ways to do this using more advanced techniques, but basic mapping will suffice.

In a new src/utils/parse-properties file:

import { QueryDatabaseResponse } from '@notionhq/client/build/src/api-endpoints';

export type Post = {
  id: string;
  title: string;
};

export const parseProperties = (database: QueryDatabaseResponse): Post[] =>
  database.results.map((row) => {
    const id = row.id;
    const titleCell = row.properties.Title.title;
    const title = titleCell?.[0].plain_text;
    return { id, title };
  });

Now we can leverage NextJS server rendering feature via getStaticProps to prefetch and render our Home page. In index.tsx:

import type { NextPage } from 'next';
import Head from 'next/head';
import { queryDatabase } from '../src/api/query-database';
import styles from '../styles/Home.module.css';
import { parseProperties, Post } from '../src/utils/parse-properties';

type HomeProps = {
  posts: Post[];
};

const Home: NextPage<HomeProps> = ({ posts }) => {
  return (
    <div className={styles.container}>
      <Head>
        <title>My Notion Blog</title>
        <meta
          name="description"
          content="Notion blog generated from Notion API"
        />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1>My Notion Blog</h1>
        <ul>
          {posts.map(({ id, title }) => (
            <li key={id}>{title}</li>
          ))}
        </ul>
      </main>
    </div>
  );
};

export async function getStaticProps() {
  const database = await queryDatabase();
  const posts = parseProperties(database);

  return {
    props: {
      posts,
    },
  };
}

export default Home;

We should now see our post titles loaded on the Home page when we run npm run dev.

NextJS + Notion Integration

🔗Finishing Up

With a few setup pieces, it's easy to setup a basic blog using Notion's API, but there are more possibilities and use cases for Notion as a CMS. Keep in mind that this may not be the best database to use in a production environment, but playing around with one of my favourite tools creates some new possibilies for non-Notion-tailored experiences.

Full Code Example Here


This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit thisdot.co

You might also like

Javascript

Getting Started with RxJS

Javascript

Testing Web Components with Cypress and TypeScript

Javascript

Web Components Integration using LitElement and TypeScript

Javascript

Navigation Lifecycle using Vaadin Router, LitElement and TypeScript