Building a blog with Notion


Why Choose Notion?

I've used quite a few blog products, but when I think about all the blogs I've used, there are always a few points that leave me unsatisfied.

  • Representative of commercialized blog platforms, Medium: Unable to customize + poor user experience
    • Poor web page editing experience
    • Unable to customize domain name (for free accounts)
    • The page layout is not bad, but the Chinese layout is average, and it does not support code block highlighting.
    • The data cannot be directly exported to Markdown, making the cost of migration to other platforms high.
  • Static blog represented by Hexo: Troublesome
    • Need to back up articles additionally
    • Even a single word change requires a deployment (automated scheduled deployment cannot achieve real-time updates).
  • Blog system representative Wordpress: Troublesome
    • It is necessary to deploy both Wordpress and the database simultaneously, which increases maintenance costs.
    • Poor editor experience
    • Can't find a theme I like (I don't know PHP, so I can't customize it🤷‍♂️)

And so far, Notion is, in my opinion, an almost perfect writing tool. Moreover, Notion's documents can be directly set to public, which can also be used directly as a blog.

However, I need: custom domain, integration with Google Analytics, custom interface, SEO...... This requires me to customize it myself. Fortunately, Notion's API is semi-public, I can directly create web pages through the API. In this way, I can use Notion as a CMS like Wordpress to manage my own blog.

Make the process of blogging simple and fun.

If you happen to use Notion as your note-taking app regularly, then the benefits of managing a blog with Notion are obvious.

I can write a blog post while taking notes, and if I ever want to modify an article, I just need to open the corresponding page and make changes. Moreover, I don't need to worry about data backup, image hosting, or service stability, as Notion will take care of everything for me.

This significantly reduces the time cost of writing a blog. Only by simplifying the process can you keep going.

Furthermore, with Notion's rich components, you can create more vibrant pages compared to traditional Markdown documents.


Obtain the API

The API of Notion is very easy to obtain. During the process of loading a webpage, you can directly see the API information of the page loading in the browser's developer panel.

The three main APIs used

loadPageChunk: Retrieve all information of a page

getRecordValues: Retrieve information of a single Block

queryCollection: Retrieve the collection component data from Notion, such as table, list.


The page Block of Notion is tree-shaped, but the data requested is in the form of a list. It needs to be converted into a tree shape locally, and then handed over to the rendering function for rendering.

Each Block has a type field, which can determine what component is ultimately rendered. So overall, it's about defining a NotionBlock component, letting this component decide what content to render. Blocks at all levels also just hand over the child block to NotionBlock for rendering.

In this way, it effectively decouples the page and the components. I only need to develop the components I need. In the future, if there are more needs, such as embedding Google Map, Twitter, I only need to adapt it again, and then register the Block Type to NotionBlock.

Server-side Rendering

For better SEO performance, the half-developed React application was refactored using next.js. Also, the Notion API requests were placed on the server side, and caching was applied to the article list and articles to maximize page loading speed as much as possible.

Although server-side data requests will prolong the white screen time during each page load; on the other hand, because it directly accesses the server, it eliminates the problem of slow access speed of Notion in China.

At the same time, generate sitemap.xml in real-time directly on the backend to improve SEO efficiency.


I used the Google App Engine in Hong Kong, which is very convenient for directly deploying Node.js projects, and the standard container is also free.

For better loading speed, I used Cloudflare's CDN service (I didn't bother to register for domestic acceleration), the speed is still acceptable, and the page can be displayed within 5 seconds for the first time in mainland China.


The website is now accessible. Although the overall speed is not as fast as the Github Pages static blog I used before, it is still acceptable. The main performance bottleneck lies in the synchronous request to the Notion API and real-time page rendering, so the optimization direction is to asynchronously request the API, cache data, and cache rendering results.

Fortunately, Notion's API can obtain the last modification time of the article, which allows for monitoring changes in the article in the background and timely updating the cache.


Import Document

Notion comes with a built-in Markdown import tool.

Import all your previous markdown documents into Notion, but there are a few points to note.

  • Notion does not support multi-level headers in Markdown, it only supports first-level # and second-level ## headers. This practice is the same as Medium, requiring modifications to some original documents.

    When writing, it's also important to appropriately structure your articles. Although it may be inconvenient, excessive structuring is indeed not the best practice for writing articles.

  • Notion does not support the native Table of Markdown, instead, it requires the use of Notion's collection component. The original data structure imported will be messed up, and it needs to be manually readjusted.
  • Notion does not support embedded HTML. If the original document contains embedded HTML, it will turn into plain text on Notion, and Notion does not provide corresponding components to render HTML (embedding Codepen might be a solution, but it's quite troublesome).

    So if you need to render on the blog, you need to set the corresponding rules yourself, and I used the code component to save the HTML code.

    if (language === 'HTML' && codeText.startsWith("<!--render-->")) {
         return <div dangerouslySetInnerHTML={{__html: codeText}}/>

    By marking the code blocks, the frontend can render in real time.

Manage documents with tables

Create a Table Page, and pull all the imported articles into this page.

Set up the corresponding columns

  • Tags
  • Publication Date

    Unlike the built-in creation date and modification date in Notion, defining your own date allows you to modify it as needed for sorting purposes.

  • Article Name

    Unlike the title, it is used for URL display, such as, it displays a name, not a string of UUID.

  • Publish or not

    As a switch to control whether the article is displayed or not.

Create View

Notion's Table has a view function similar to a database. You can create a view and set filters and sorts for it. In this way, you only need to pull this view on the frontend, and it can automatically sort and display the corresponding content.


At this point, the blog has basically taken shape.


This time, I made up my mind, spent a certain amount of time, but developed a personal blog in one go (perfectly cooled down, solving most of the problems I encountered on other platforms, and even has many unique advantages of Notion. In the future, only moderate maintenance is needed, and I no longer have to worry about which blog platform to use.

Although there are still many customizable aspects that would allow me to do more configuration work through Notion, I decided to let it be for now, considering the considerable amount of time I've already spent on it. My basic needs have been largely met, and I'll think about further improvements in the future.