Notion is a great tool for writing and managing documents. I love writing in Notion because it provides a focused view where I can get all my thoughts down with minimal distraction. Because I love the simple, organized style of Notion, I decided to model my new website off of the Notion interface. I also wanted to use Notion as a content management system, so I could write my blog posts directly in Notion, click a button to publish it when I'm ready, and have it immediately appear on the site. I love the seamless integration I've been able to achieve with Notion and want to show you how I did it!
The Stack
First I want to tell you a little about the stack I've used to create wilsonstaley.dev. The site is a React application, scaffolded and built with Vite. I used Tailwind for styling mainly because I'm not too familiar with it and wanted to test it out on a new project (I'll have to share my thoughts on Tailwind in a future post 🙂).
I'm hosting the site on Netlify, and all the blog post data is client-side rendered. So when you land on the homepage, a call is made to retrieve all the blog posts from Notion and populate the page. While it would probably be more performant to use something like Nextjs to statically build all the blog pages, this does avoid an issue with Notion images where the links expire after a short period of time. I am however using SWR to get some caching and preloading benefits.
And of course, for this to work, you'll need a Notion account! Let's talk about how I've structured the data in Notion…
Notion Database
To store all of my blog posts, I created a database in Notion. You can do this by clicking "New Page" and selecting "Database". In Notion, a database is a collection of related data that is organized into rows and columns. Think of it like a spreadsheet, but with more flexibility and functionality.
In my case, I set up a database where each row corresponds to a blog post. I gave it properties including "Title", "Description", "Tags", "Date", "Published", and "Slug".
These properties are pretty self-explanatory.
The "Published" checkbox is an easy way I can mark a post to be publicly visible on the site. I can write the post and see how it looks in dev, and when I'm good and ready, all I have to do is click the checkbox to make it go live. This is another perk of client-side rendering - each visitor will see the most up-to-date records from my Notion database.
The "Slug" property is also useful. I am using this to determine what URL the user sees for each post. For example, the post titled "Git Workflows 101" would be accessible at the URL wilsonstaley.dev/posts/git-workflows-101.
Retrieving the Data
Now we'll finish up our "backend" by creating a simple API with a single endpoint. This will just be an endpoint where we can retrieve either a list of blog posts or the data for an individual post.
To create this lambda function, I used a Netlify function. This is a service Netlify offers that lets you quickly deploy a serverless function in conjunction with your site. I also used Notion's SDK to easily query the database I set up.
First, let's look at an example of what it would look like to query the database for a list of blog posts:
import { Client } from "@notionhq/client";
...
const env = process.env.NETLIFY_ENV || "dev";
const notion = new Client({ auth: process.env.NOTION_API_KEY });
const databaseId = process.env.NOTION_DATABASE_ID;
const queryOptions = {
database_id: databaseId,
sorts: [
{
property: "Date",
direction: "descending",
},
],
};
//If in production, filter to only published posts
if (env === "production") {
queryOptions.filter = {
property: "Published",
checkbox: {
equals: true,
},
};
}
const posts = await notion.databases.query(queryOptions);
return {
statusCode: 200,
body: JSON.stringify(posts),
};
In this example, I am querying the Notion database and asking for the results to be sorted by date. I am also filtering the results to only return the published posts if I am in a production environment. In order for this to work, you must make sure you have created a Notion integration and shared the database with your integration.
Now, let us look at what the code might look like for retrieving data for a specific blog post:
import { Client } from "@notionhq/client";
import { NotionToMarkdown } from "notion-to-md";
...
const notion = new Client({ auth: process.env.NOTION_API_KEY });
const { id: postId } = event.queryStringParameters;
if (postId) {
const response = await notion.pages.retrieve({ page_id: postId });
const n2m = new NotionToMarkdown({ notionClient: notion });
const mdblocks = await n2m.pageToMarkdown(postId);
const mdString = n2m.toMarkdownString(mdblocks);
const responseData = {
bannerImg: response.cover.external.url,
markdown: mdString,
title: response.properties.Title.title[0].plain_text
};
return {
statusCode: 200,
body: JSON.stringify(responseData),
};
}
Here, I'm retrieving data for a specific page and using the notion-to-md package to generate Markdown for this blog post. Then, I package it into a custom response that I can use for individual blog post pages on my site.
Netlify functions are available as a custom endpoint when you deploy your site. In my case, I created an endpoint /.netlify/functions/posts
. Hitting this endpoint returns the data for all the blog posts. Adding a query param /.netlify/functions/posts?id=1d12aaeb324
gives you the data for that individual post, including the Markdown.
Rendering a Blog Page
Now that we've got our backend setup to retrieve the data from Notion, let's look at how we can render that data in a readable format.
<Layout/>
<img
src={bannerImg}
alt=""
className="block w-full h-48 md:h-56 lg:h-64 xl:h-80 object-cover object-center"
/>
<article className="max-w-screen-sm m-auto px-4 pt-4 pb-10">
<h1 className="my-6 text-4xl font-bold">{title}</h1>
<ReactMarkdown components={markdownMapping}>
{markdown}
</ReactMarkdown>
</article>
</Layout>
In this example React component, I have a page that can be used with any blog post. The page includes a banner image, a title, and the <ReactMarkdown />
component that will take all of the markdown I generated on the backend and transform it into valid HTML. The “components” attribute on the markdown component lets me specify custom components to use for each markdown element. So if I were to have a link in my markdown, I could map it to my own custom link component. If you want to learn more about how that works, you can look into the react-markdown library.
And with that, we’ve setup a really simple, yet powerful content management system using Notion. I love how easy it was to get this setup and how fast I can publish new content. I hope this tutorial was helpful and gave you some idea of how you can use Notion in your next project!
Top comments (1)
Thank you so much for sharing this fantastic Notion article! I'm truly grateful for the opportunity to enjoy such quality content. Keep sharing the goodness!