DEV Community

Cover image for How to Create a Blog with NextJS, Notion, and Notion API
Yunus Ertürk
Yunus Ertürk

Posted on • Edited on • Originally published at blog.yunuserturk.com

How to Create a Blog with NextJS, Notion, and Notion API

In this post, I want to try how we can create a blog management system using notion as a database. I have created a post on how to create a blog with airtable, I used airtable in that project. Now I'm doing it with notion.

I wrote this post to my own blog first, and I want to publish it here too.

At first, we can find everything about notion; https://developers.notion.com/ address

Create a Notion Integration for Our Blog

I have looked at the documentation and let's start. Firstly we should create an integration from https://www.notion.so/my-integrations

notion

Create a new integration on this page. I have created an integration named myblog, and these are my settings;

basic information

After these steps notion will give you a notion token, copy this token to a secure place, and we will use it for connecting our data.

After that, we have to create a page and convert it to a database to use for our blog. I have created a notion page -- database full page. This is what I get;

new

There is a share button top right, Click on the Sharebutton and use the selector to find your integration by its name, then click Invite.

Our integration has an access to our database now, last step we need the database id. It's the part of your URL;

Mine is; https://www.notion.so/yunuserturk/f9dfc334e89e4c2289b9bc98884b5e80

And the database id is; f9dfc334e89e4c2289b9bc98884b5e80

Now we have to create our app and we can use these notion access.

Create a NextJS App

The easiest way to get started with Next.js is by using create-next-app .

Create a project with VS Code, open the terminal and use the command; npx create-next-app

Install tailwindcss;

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Enter fullscreen mode Exit fullscreen mode

Create a sketchy UI, I copied the code from the airtable project.

import Head from "next/head";

export default function Home() {
  return (
    <div>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <div className="container mx-auto w-full sm:w-10/12 md:w-8/12 lg:w-6/12 xl:w-6/12 p-4">
          <h1 className="font-bold text-2xl">Welcome to my blog</h1>
          <div className="flex flex-col p-5 rounded-lg border-2 border-gray-200 mt-10">
            <h2 className="text-xl font-bold">Post Title</h2>
            <div>Post Content will be here</div>
            <span className="text-sm mb-6">Post Date</span>
          </div>
        </div>
      </main>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

How to Use Notion API and Show the Content

Our homepage is ready, now we have to fetch the data to show it on our page. Let's install notion client SDK with npm install @notionhq/client, run this command on terminal. Create a file lib>notion.js and we will use our database here. With the help of the documentation, I created a starter code.

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

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

export const getDatabase = async (databaseId) => {
  const response = await notion.databases.query({
    database_id: databaseId,
  });
  return response.results;
};

export const getPage = async (pageId) => {
  const response = await notion.pages.retrieve({ page_id: pageId });
  return response;
};

export const getBlocks = async (blockId) => {
  const blocks = [];
  let block;
  while (true) {
    const { results, next_block } = await notion.blocks.children.list({
      start_block: block,
      block_id: blockId,
    });
    blocks.push(...results);
    if (!next_block) {
      break;
    }
    block = next_block;
  }
  return blocks;
}

Enter fullscreen mode Exit fullscreen mode

We'll import this to the index page and fetch the data and show the posts on the home page. With the notion API, getpage function only fetches the properties of the page. We can't retrieve the page blocks. We have to create another function and use API for retrieving page content. Notion called them blocks and has another function for it.

My final index.js page is like that;

import Head from "next/head";
import { getDatabase } from "../lib/notion";
import Link from "next/link";

export default function Home({ posts }) {

  return (
    <div>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <div className="container mx-auto w-full sm:w-10/12 md:w-8/12 lg:w-6/12 xl:w-6/12 p-4">
          <h1 className="font-bold text-2xl">Welcome to my blog</h1>
          {posts.map((post) => {
            const date=new Date(post.created_time).toLocaleString("en-Us",{
              month: "long",
              day: "numeric",
              year: "numeric",
              })
            return (
              <div className="flex flex-col p-5 rounded-lg border-2 border-gray-200 mt-10" key={post.id}>
                <h2 className="text-xl font-bold">{post.properties.Name.title[0].plain_text}</h2>
                <span className="text-sm">{date}</span>
                <Link href="/[post]" as={`/${post.id}`}>
                  <a className="text-sm">Read more</a>
                </Link>
              </div>
            );
          })}
        </div>
      </main>
    </div>
  );
}

export const getStaticProps = async () => {
  const database = await getDatabase("f9dfc334e89e4c2289b9bc98884b5e80");

  return {
    props: {
      posts: database,
    },
    revalidate: 1,
  };
};

Enter fullscreen mode Exit fullscreen mode

Notion gives us the blocks with API with plenty of properties. As you wish you can use which of them you want. But I just use the text and just for these blocks; paragraphs and headers. You can adjust the blocks you will use according to the project you are doing. (We will use it on the single post page.)

Here is the single post page file;

import { getDatabase, getPage, getBlocks } from "../lib/notion";
import Head from "next/head";

const renderBlock = (block) => {
  const { type } = block;
  const value = block[type];

  switch (type) {
    case "paragraph":
      return <p className="mb-4">{value.rich_text[0].plain_text}</p>;
    case "heading_1":
      return <h1 className="font-bold text-3xl  mb-2">{value.rich_text[0].plain_text}</h1>;
    case "heading_2":
      return <h2 className="font-bold text-2xl  mb-2">{value.rich_text[0].plain_text}</h2>;
    case "heading_3":
      return <h3 className=" font-bold text-xl mb-2">{value.rich_text[0].plain_text}</h3>;
  }
};

export default function Post({ post, content }) {
  const date = new Date(post.created_time).toLocaleString("en-Us", {
    month: "long",
    day: "numeric",
    year: "numeric",
  });
  return (
    <div>
      <Head>
        <title>{post.properties.Name.title[0].plain_text}</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <div className="container mx-auto w-full sm:w-10/12 md:w-8/12 lg:w-6/12 xl:w-6/12 p-4">
          <h1 className="font-bold text-2xl">{post.properties.Name.title[0].plain_text}</h1>
          <span className="text-sm">{date}</span>
          <img src="<https://picsum.photos/800/200>" alt="random image" className=" max-w-full" />
          <div className="mt-10">
            {content.map((block) => {
              console.log(block);
              return <div key={block.id}>{block.type === "page_break" ? <hr /> : <div>{renderBlock(block)}</div>}</div>;
            })}
          </div>
        </div>
      </main>
    </div>
  );
}

export const getStaticPaths = async () => {
  const database = await getDatabase("f9dfc334e89e4c2289b9bc98884b5e80");
  const paths = database.map((post) => {
    return {
      params: {
        post: post.id,
      },
    };
  });
  return {
    paths,
    fallback: false,
  };
};

export const getStaticProps = async ({ params }) => {
  const postId = params.post;
  const post = await getPage(postId);
  const content = await getBlocks(postId);

  return {
    props: {
      post,
      content,
    },
    revalidate: 1,
  };
};

Enter fullscreen mode Exit fullscreen mode

Here is the final post page;

final

Deployment and the result

I deployed it to vercel finally. It's a very basic use of Nation API, If you create something more different you can use it this way.

You can see al the code from the github repo: https://github.com/yunuserturk/notion-blog

Here is the demo of final result: https://notion-blog-yunuserturk.vercel.app/

Top comments (4)

Collapse
 
sargnec profile image
Necmettin Sargın

Awesome 👍 Great content. I was looking for something like this to use it make a portfolio site (a portfolio that will give a info about a game developer and present his games ) Is this possible with notion as a backend? Eline sağlık 😀

Collapse
 
yunuserturk profile image
Yunus Ertürk

Yes, you can create the structure right that.

Collapse
 
siddharth0x profile image
Siddharth

Great explanation

Collapse
 
yunuserturk profile image
Yunus Ertürk

Thank you, Siddharth.