when I was creating my personal website (the one you are reading this), I wanted to also have a blog, some place where I could post some of my stuff, just like this one you are reading. But I got stuck thinking that I didn’t want to:
then, it all started, the mix of feelings that every developer has gone through:
and there I was, on the Notion API documentation, trying to understand how it works, what it gives from requests, and how to make it work with my framework of choice: Next.js
instead of telling you all the ups and downs or trying to tell you to figure it out yourself because that way you will learn more, I’ll just show you how I did it. sometimes, we don’t have the time or energy to learn something from scratch, and that is okay! but before we start, here are something that I’ll need you to keep in mind (just so I don’t feel too judged on my lack of attention to details on the code…):
here is the code of the class I created to simplify the usage
import { Client } from "@notionhq/client";
export class NotionApi {
private notion: Client;
constructor() {
this.notion = new Client({
auth: <YOUR API KEY>,
});
}
async getDatabase(databaseId: string, filter: any = undefined) {
const response = await this.notion.databases.query({
database_id: databaseId,
filter: filter,
});
return response.results;
}
async getActivePosts() {
const filterByActivePosts = {
property: "active",
checkbox: {
equals: true,
},
};
return this.getDatabase(
<YOUR DATABASE ID>,
filterByActivePosts
);
}
async getPageContent(pageId: string) {
const response = await this.notion.blocks.children.list({
block_id: pageId,
page_size: 100,
});
}
}
the main getaway from the code above is:
you will need to instantiate your client
this.notion = new Client({
auth: <your notion api secret>,
});
const response = await this.notion.databases.query({
database_id: <your database id>,
filter: {
property: "active",
checkbox: {
equals: true,
},
},
});
return response.results;
and with the page id, you can query all the info on that page
async getPageContent(pageId: string) {
const response = await this.notion.blocks.children.list({
block_id: pageId,
page_size: 100, // returns up to 100 blocks in a page
});
}
ok! we can already get the pages (posts) from our database. So now, we can just render them directly on our website, right?! … right?…
nope, sorry.
For the block below
the block object returned from the api is
{
"object": "block",
"id": "...",
"parent": {
"type": "page_id",
"page_id": "..."
},
"created_time": "...",
"last_edited_time": "...",
"created_by": {
"object": "user",
"id": "..."
},
"last_edited_by": {
"object": "user",
"id": "..."
},
"has_children": false,
"archived": false,
"type": "heading_2",
"heading_2": {
"rich_text": [
{
"type": "text",
"text": {
"content": "Lacinato kale",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
},
"plain_text": "Lacinato kale",
"href": null
}
],
"color": "default",
"is_toggleable": false
}
}
some information was removed, but the prop key was kept.
so, a lot of information for a simple block, that means that we have to sanitize the information.
export const parsePageContent = (content) => {
const parsedContent = content.map((block) => {
const {
type,
has_children,
heading_1,
heading_2,
heading_3,
paragraph,
to_do,
toggle,
callout,
numbered_list_item,
bulleted_list_item,
id,
table,
quote,
code,
} = block;
switch (type) {
case "heading_1":
return {
type: "heading_1",
text: heading_1.rich_text[0].text.content,
color: heading_1.color,
props: heading_1.rich_text[0].annotations,
};
case "heading_2":
return {
type: "heading_2",
text: heading_2.rich_text[0].text.content,
color: heading_2.color,
props: heading_2.rich_text[0].annotations,
};
<there are more cases here...>
case "paragraph":
if (paragraph.rich_text.length == 0) {
return {
type: "empty_line",
};
} else if (paragraph.rich_text.length === 1) {
switch (paragraph.rich_text[0].type) {
case "text":
return {
type: "paragraph",
text: paragraph.rich_text[0].text.content,
color: paragraph.color,
props: paragraph.rich_text[0].annotations,
};
case "code":
return {
type: "code",
text: paragraph.rich_text[0].plain_text,
color: paragraph.color,
props: paragraph.rich_text[0].annotations,
};
default:
return {
message:
"This block type is not supported",
type: paragraph.rich_text[0].type,
};
}
} else {
let textList = [];
for (let i = 0; i < paragraph.rich_text.length; i++) {
let { text, href } = paragraph.rich_text[i];
text = text.content;
let textObj = {};
if (href) {
textObj = {
type: "link",
text: text,
href: href,
};
} else {
textObj = {
type: "text",
text: text,
href: href,
};
}
textList.push(textObj);
}
return {
type: "text_list",
text: textList,
};
}
<there are more cases here too...>
}
});
return parsedContent;
};
as you can see, a lot of code. there were some edge cases too, like the “paragraph” case above.
but, it did the job. it returns a list of objects that has everything that I needed to render it. So now, what was left to do was receive this list and render what was necessary. and again… another really big switch case
export const getComponent = (block: any) => {
switch (block.type) {
case "heading_1":
return <h1>{block.text}</h1>;
case "heading_2":
return <h2>{block.text}</h2>;
...
case "empty_line":
return <br />;
case "paragraph":
const { bold, code, color, italic, strikethrough, underline } =
block.props;
const colorProps = color.split("_");
let textBlock;
if (bold) textBlock = <strong>{block.text}</strong>;
else if (code) textBlock = <code>{block.text}</code>;
else if (italic) textBlock = <em>{block.text}</em>;
else if (strikethrough) textBlock = <del>{block.text}</del>;
else if (underline) textBlock = <u>{block.text}</u>;
else textBlock = <>{block.text}</>;
return <p colors={colorProps}>{textBlock}</p>;
case "numbered_list_item":
if (block.list.length > 1)
return (
<ol>
{block.list.map((li: string, i: number) => (
<li key={i}>{li}</li>
))}
</ol>
);
return <></>;
case "divider":
return <hr />;
...
default:
console.warn(
`This block (${block.type}) type is not supported.`
);
return <></>;
}
};
and with that, after hours reading returned JSON from notion’s api, I managed to write this post on my notion and present it here to you!
Thank you for reading all of it!
If the information on this post was useful to you, maybe it will be useful to someone else too! Don’t forget to share!