Using Notion as a Headless CMS

Introduction#

Notion has become increasingly popular as a versatile tool for everything from taking notes to managing projects, and when combined with it’s API we’re able to automate workflows, create custom apps, and even use Notion as a headless CMS for websites and blogs.

With the help of tools like Nuxt, we can easily create dynamic websites and blogs that are powered by Notion. In this tutorial, we will explore how to use Notion as a headless CMS with Nuxt 3. We will cover setting up the Notion API, creating a database of images in Notion, and building a simple website that displays our content.

By the end of this tutorial, you should have a good understanding of how to use Notion as a headless CMS with Nuxt 3.

Step 1: Project Setup#

Create a new Nuxt project and install dependencies:

npx nuxi init notion-cms -y && cd notion-cms && npm install

Run locally:

npm run dev

Image description

From here, we can begin configuring notion.

Step 2: Create Notion Database#

Create a new Notion page. This page will house our image database:

Image description

Add an inline database called ‘Images’ using the gallery layout:

Image description

Next, we’ll create a ‘Files & media’ property to upload an image:

Image description

We can set the gallery preview thumbnail to display the uploaded file:

Image description

Let’s upload a few more for good measure:

Image description

Our database it looking great! However, we need to hook up the database to an integration before we can access it from the API.

Step 3: Create a new Notion Integration#

Visit notion.so/my-integrations to create a new integration.

Once it’s created, an ‘Internal Integration Token’ will be generated:

Image description

We’ll need this token later on to access the database from the client, so we can copy and paste it somewhere secure for now.

I’ve named my integration ‘CMS’ with a custom thumbnail:

Image description

Set integration type as ‘internal’:

Image description

Since we only want to fetch images and not update or delete anything from the client, we’ll just check ‘Read Content’:

Image description

Step 4: Connect Integration to Database#

Head back to the database page we created in Step 2.

From here, we can click ‘Add connections’ where we’ll find the ‘CMS’ integration we created in Step 3:

Image description

With that, we’re all set on the Notion side. Let’s switch gears and jump into the code.

Step 5: Setup Environment Variables#

This is where our ‘Internal Integration Token’ from Step 3 comes in:

Image description

In the root of your nuxt project, create a .env file. This file will contain our internal integration token and the ID of the database we created in Step 2.

Image description

The ID of the database can be found in the URL:

Image description

NOTION_API_KEY should be set as the secret internal integration key.

Your .env file should look something like this:

Image description

Step 6: Nuxt Server Setup#

Create a new file server/api/gallery.get.js:

Image description

The handler in this file will be called when the user hits the endpoint localhost:3000/api/gallery

Let’s return the following dummy data to check if it’s working:

// gallery.get.js

const test_data = [
  {
    id: 1,
    name: 'item1',
  },
  {
    id: 2,
    name: 'item2',
  },
  {
    id: 3,
    name: 'item3',
  },
]

export default defineEventHandler(() => test_data)

We should be able to see the JSON response in the browser now 🙌🏾

Image description

From here, we can install the Notion client and begin making requests.

Step 7: Notion API Client Setup#

Install the notion client:

npm install @notionhq/client

Let’s pull in the environment variables for authorization and try fetching the database matching the ID in our .env:

// gallery.get.js

import { Client } from '@notionhq/client'

const notion = new Client({ auth: process.env.NOTION_API_KEY })
const image_database_id = process.env.NOTION_DATABASE_ID

let payload = []

async function getImages() {
  const data = await notion.databases.query({
    database_id: image_database_id,
  })
  return data
}

getImages().then((data) => {
  payload = data.results
})

export default defineEventHandler(() => payload)

Now when we visit localhost:3000/api/gallery, we can see the data returned from our Notion database \( ̄ ▽  ̄)/

Image description

We don’t need quite so much data though… we only care about the image url in this case, so let’s isolate the data we need before sending back to the front-end:

Image description

Now we only receive the neccesary data from Notion:

Image description

Step 8: Render the Images#

Finally, we can fetch the image URLs from the server and render them on the front-end ~ let’s add a bit of styling as well:

// app.vue

<script setup>
const state = reactive({
  images: [],
});

const res = await fetch("http://localhost:3000/api/gallery");

res.json().then((images) => {
  console.log(images);
  state.images = images;
});
</script>

<template>
  <main>
    <h1>ヽ(♡‿♡)ノ</h1>
    <div v-for="(image, index) in state.images" :key="index">
      <img :src="image" />
    </div>
  </main>
</template>

<style>
* {
  font-weight: normal;
}
body {
  padding: 0;
  margin: 0;
  font-family: monospace;
  color: white;
}
h1 {
  position: absolute;
  margin-left: auto;
  margin-right: auto;
  top: 64px;
}
img {
  width: 200px;
  height: 200px;
  object-fit: cover;
  border-radius: 8px;
  cursor: pointer;
  transition: 600ms cubic-bezier(0.16, 1, 0.3, 1);
}
img:hover {
  transform: scale(1.1);
  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.2);
}
main {
  display: flex;
  gap: 36px;
  align-items: center;
  justify-content: center;
  height: 100vh;
  width: 100vw;
  background: linear-gradient(black, #111);
}
</style>

We can now see the images rendered on the front-end:

Image description

Conclusion:#

Notion is a powerful tool and works surprisingly well as a headless CMS.

My personal website is powered by Notion, and while there are some trade-offs, Notion has become my goto for projects requiring a light weight CMS.

I would love to try some alternatives so please let me know your favorite CMS for small-scale projects!

Full Source Code

References#