Thursday, June 20, 2024

Use Notion as your CMS along with Next.js

Programming LanguageUse Notion as your CMS along with Next.js

Note: This is not a step-by-step tutorial on how to integrate the Notion API/SDK in Next.js. Instead, it provides an overview of the process involved and aims to offer insights into the concepts and technologies used. For those seeking detailed tutorials, various resources and documentation are mentioned below.

In this article, we explore the process of harnessing the power of the Notion API to extract data from its pages and databases. By utilizing AWS Lambda functions, we transform this data into JSON files, which are subsequently stored in an S3 bucket. Leveraging the CloudFront Content Delivery Network (CDN), we ensure efficient and speedy access to this cached JSON data. Finally, we demonstrate how this dynamic content is seamlessly integrated into a Next.js application, enhancing its performance and user experience.

But keep in mind, this process is only suitable when your data does not change frequently, such as a portfolio

Notion API

Notion has become a popular choice for organizing information and collaborating on projects due to its versatility and user-friendly interface. The Notion API provides endpoints to fetch data from pages, databases, and other elements within a workspace.
Although the JSON response from the API is not HTML, we need to convert the data to HTML text (or whichever format you like). Here is a simple utility I made for that purpose:

// annotations is the response from Notion API
const getHTMLTagsFromAnnotations = (
  annotations: TextRichTextItemResponse[""],
  link: string | null,
  keepLink = true
) => {
  let closingTag = "",
    openingTag = "";
  Object.keys(annotations).forEach((tag) => {
    if (annotations[tag] === false) return;
    switch (tag) {
      case ""
        openingTag += "<b>";
        closingTag += "</b>";
      case ""
        openingTag += "<i>";
        closingTag += "</i>";
      case ""
        openingTag += "<s>";
        closingTag += "</s>";
      case ""
        openingTag += "<u>";
        closingTag += "</u>";
      case ""
        openingTag += "<code>";
        closingTag += "</code>";
  if (link && keepLink) {
    openingTag += `<a href="${link}">`;
    closingTag += "</a>";
  return {

let description = "";
// rich_text is one of the data types in Notion
result.rich_text.forEach((text) => {
    const { closingTag, openingTag } = 
    description += 
Enter fullscreen mode

Exit fullscreen mode

Since images on Notion expire a certain amount of time, one can download them while generating the resulting JSONs in Lambda store them in S3, and use that link.

Generating JSONs

I had three pages for which I needed to generate data. Since fetching the data for all the pages in a single lambda would take significantly more time than parallelizing the process.

So, instead of a single lambda, I created three lambdas which would each fetch data for a single page and by using their function invocation URL, I call them simultaneously in a single lambda using Promise.all.

Once the data is fetched and processed, I store the result as a JSON file in an S3 bucket. Which is then eventually fetched via CloudFront CDN.

But why?

I could have altogether removed the process of generating JSONs and used React Server Components in Next.js to dynamically get the required data, but that would mean that:

  • I have to call the Notion API every time my page is requested
  • Or, I have to maintain a database and implement an admin dashboard (that would also mean implementing authentication)

I initially started with Firebase Admin, but it always end up choosing between Firebase or Next. So, after battling with Firebase SDK initialization (#44966) for quite a long time, I had to ditch it and look for alternatives. Supabase looked good but was too much for my needs.
Eventually, I settled with Notion as it gave the information I required as well as a nice UI to manage that informations, that too for free.

Lastly, I did not want to have my page load times be too much. So, instead of fetching data from the Notion API, I fetch the data from a JSON file, which is served via a CDN making the initial server call much faster.

Also, this would seem like I could have just statically exported the Next.js app. However, resulted in losing some features which I required and needed to host a server for setting up NGINX, when I could have just deployed on Vercel.

This method will also work if you statically export and do not require those features, and use a static hosting service such as Amazon S3 (for free)


lighthouse report

By harnessing the capabilities of the Notion API, AWS Lambda, S3, CloudFront CDN, and Next.js, developers can create dynamic and performant applications that leverage Notion’s rich content ecosystem. This article serves as a guide for integrating Notion data into custom applications, enabling enhanced productivity and user experiences.

This represents just one of the numerous approaches to implementing such a solution. However, this particular method aligns well with my preferences and has helped me to learn new things. I remain receptive to alternative solutions and welcome any suggestions others may have.


During my search for deploying Lambdas via GitHub actions, I came across a tutorial that utilized ncc for converting TypeScript and bundling. While ncc is effective, I discovered esbuild, which proved to be significantly faster and perfectly suited to my requirements.


Check out our other content

Check out other tags:

Most Popular Articles