DEV Community

Cover image for Using Notion as a Headless CMS with Nuxt
Trent Brew
Trent Brew

Posted on • Edited on

Using Notion as a Headless CMS with Nuxt

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
Enter fullscreen mode Exit fullscreen mode

Run locally:

npm run dev
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

Top comments (11)

Collapse
 
madebyfabian profile image
Fabian B.

Awesome! Thank you so much for this extended article! I am currently starting to develop a nuxt layer that provides utilities on how to integrate wordpress as a headless CMS. Maybe this could be used to also create a nuxt-notion layer to get people started even faster, what do you think?

Collapse
 
trentbrew profile image
Trent Brew

Sounds interesting! It'd be nice to use Wordpress to serve content while having full freedom on the front-end through Nuxt. Any updates on the project?

Collapse
 
madebyfabian profile image
Fabian B. • Edited

@trentbrew Hey there, glad you're interested, currently don't have much time for it, so the layer is in alpha/beta status, see github.com/madebyfabian/nuxt-wordp.... Also check out my blog post about wordpress with nuxt madebyfabian.com/blog/how-to-creat... and the repo of my website, in which you can see how I integrated wordpress into a full nuxt website:
github.com/madebyfabian/madebyfabian I want to extract many functionalities from this website repo to be available in the layer as soon as I have time for.

Collapse
 
thewebbeckons profile image
Jesse Beck

Great article; but I also wanted to come back here to say awesome personal website :)

Collapse
 
trentbrew profile image
Trent Brew

Thank you Jesse 💙

Collapse
 
thomasbnt profile image
Thomas Bnt

Using Notion as a Headless CMS with Nuxt

🤯🤯🤯

I only stayed with the title. The fact of mixing the two, it seems to me not obvious, to read later 🤩

Collapse
 
daxtersky profile image
Miko

Cool art, thanks! What would be the trade-offs of using Notion in your opinion?

Collapse
 
trentbrew profile image
Trent Brew

thanks Miko(: I think scalability is the biggest trade-off since the API limits how frequently you can make requests and how many pages you can get per request but this hasn't been an issue for me. You're also currently unable to upload files using the API

Collapse
 
faridsa profile image
farid

Cool! Really interesting approach, I will dig a little to see if it will work with Notion Blog section too. Thank you for your post!

Collapse
 
wouter_muller_100e7552f4c profile image
Wouter Muller

Would this also work with a regular Vue 3 app instead of Nuxt?

Collapse
 
trentbrew profile image
Trent Brew • Edited

You might use Node & Express with vanilla Vue 3 – This video is a great example using express but with EJS for the frontend: https://www.youtube.com/watch?v=zVfVLBjQuSA&t=2485s