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
Create a new integration on this page. I have created an integration named myblog, and these are my settings;
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;
There is a share button top right, Click on the Share
button 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
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>
);
}
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;
}
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,
};
};
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,
};
};
Here is the final post page;
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)
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 😀
Yes, you can create the structure right that.
Great explanation
Thank you, Siddharth.