Using Notion as the CMS for Next.js
When building my personal homepage with Next.js, I decided to use Notion as the CMS. In this post, I’ll share why I chose Notion, some technical challenges I faced, and how I solved them.
Why Use Notion?
Notion is a tool I already use daily for organizing my notes, so using it as a CMS felt natural. Here are some of the key benefits and limitations I’ve experienced:
Pros:
- Since I already use Notion, there’s no learning curve.
- I can edit content anywhere—on my PC or smartphone.
- As a non-native English speaker, editing content in the browser with tools like Grammarly is a huge plus.
Cons:
- Notion doesn’t support embedding highly customized UI components directly into blog content.
Because I don’t plan to have highly customized UI components in my post, the flexibility and ease of use made it an ideal choice for my needs.
Using the Notion API
Notion provides an official API along with a JavaScript SDK to manage content programmatically. While the SDK offers basic type safety, I needed more robust validation for my custom table properties (e.g., tags, published status). To handle this, I used Zod to add stricter type safety. This ensures my data structure remains consistent and reliable.
Handling Images
One challenge with using the Notion API is that image URLs retrieved from it expire after one hour. To work around this limitation:
- I upload images from Notion to an image CDN (Cloudinary).
- The blog retrieves images from the image CDN instead of directly from Notion.
This approach ensures that images are always available and served efficiently through a CDN.
Maintaining List Item Semantics
When rendering lists in HTML, each li
element must be wrapped in either a ul
or li
tag for proper semantics. However, the Notion API returns list items individually without wrapping them. To fix this: I programmatically group list items and wrap them in ul
or ol
tags during the rendering process (the implementation). This ensures the generated HTML is valid and accessible.
Static Rendering with RSC
I chose to fully statically generate my blog and use Next.js’s App Router. This approach brings several benefits:
- CDN Caching: Static HTML can be cached at the CDN level, improving load times.
- Reduced JavaScript: By leveraging React Server Components (RSC), less JavaScript is sent to the browser, resulting in faster performance.
Static rendering combined with RSC helps deliver a highly optimized user experience.
Revalidating Post Data
Since all posts are statically generated, keeping them up-to-date requires revalidation. Thankfully, Notion recently introduced Webhooks, which enables me to revalidate direcly from Notion:
- I created a Next.js API route that calls
revalidatePath
to regenerate specific pages. This will be the Notion webhook URL. - In Notion, I set up automation to trigger a webhook whenever any property changes.
- Additionally, I added a manual trigger button for cases when the post body is updated or when immediate revalidation is needed.
Final Thoughts
Using Notion as a CMS for my Next.js homepage has been an exciting experiment. While there are some limitations, the flexibility of Notion makes it a powerful solution for lightweight content management.
Finally, here is the source code of my homepage: https://github.com/shawnrivers/home-page.