Using Notion as a Headless CMS with Nuxt
Tags: nuxt, vue, cms, tutorial
Published: 2023-01-06 20:58:38

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:

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

Run locally:

```bash
npm run dev
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/25etmxdc0t0x3e72cdwq.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8866oqefvu4fb9yp39r5.png)

Add an inline database called 'Images' using the gallery layout:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kh5rcglbuo08p4bdf3yi.gif)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dlropxgm4dv97tn19zyy.gif)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0wef1f1kt94kxrq16z1i.gif)

Let's upload a few more for good measure:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ue4dvwopzbhyp23tta02.png)

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](https://www.notion.so/my-integrations) to create a new integration.

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1o8s5nqlfi0ismb3p4ke.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/21ejx0e9jzsa7gmk06b2.png)

Set integration type as 'internal':

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rppgkeb7y7dota74a1pq.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mxnig126i7ctqstn8qnw.png)

# 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xif3j8pohz5nsl1pdnk3.gif)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1o8s5nqlfi0ismb3p4ke.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74bhxj1gypufisxgnc5d.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1xewn7cl15uh1zzbhhb9.gif)

`NOTION_API_KEY` should be set as the secret internal integration key.

Your `.env` file should look something like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nbuxofkupi6huj6ih1b.png)

# Step 6: Nuxt Server Setup

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/at4twk3ce5rst6adyk52.png)

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:

```js
// 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9fuqmz4c9fe7pmh9o98.png)

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

# Step 7: Notion API Client Setup

Install the notion client:

```bash
npm install @notionhq/client
```

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

```js
// 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cgshfexmqtxk7jxhi5i9.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dcd1nrtslmuy24p6u1c2.png)

Now we only receive the neccesary data from Notion:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1y5ow2hc6rbspz8dds6o.png)

# 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:

```js
// 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9r7leyf9dmd2mvwq2we.gif)

# Conclusion:

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

My [personal website](https://trentbrew.com) 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](https://github.com/trentbrew/notion-cms-tutorial)

# References
- [Notion API Docs](https://developers.notion.com/reference/intro)
- [Nuxt Server Docs](https://nuxt.com/docs/guide/directory-structure/server)