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
From here, we can begin configuring notion.
Step 2: Create Notion Database
Create a new Notion page. This page will house our image database:
Add an inline database called 'Images' using the gallery layout:
Next, we'll create a 'Files & media' property to upload an image:
We can set the gallery preview thumbnail to display the uploaded file:
Let's upload a few more for good measure:
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:
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:
Set integration type as 'internal':
Since we only want to fetch images and not update or delete anything from the client, we'll just check 'Read Content':
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:
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:
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.
The ID of the database can be found in the URL:
NOTION_API_KEY
should be set as the secret internal integration key.
Your .env
file should look something like this:
Step 6: Nuxt Server Setup
Create a new file server/api/gallery.get.js
:
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 🙌🏾
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 \( ̄▽ ̄)/
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:
Now we only receive the neccesary data from Notion:
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:
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!
Top comments (11)
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?
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?
@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.
Great article; but I also wanted to come back here to say awesome personal website :)
Thank you Jesse 💙
🤯🤯🤯
I only stayed with the title. The fact of mixing the two, it seems to me not obvious, to read later 🤩
Cool art, thanks! What would be the trade-offs of using Notion in your opinion?
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
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!
Would this also work with a regular Vue 3 app instead of Nuxt?
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