content/tutorials/4.migration/migrate-from-notion-to-directus.md
As a developer advocate and freelance coach, I share valuable content with my audience via my blog. When creating the blog, I needed a Headless CMS that would integrate nicely with a Next.js Pages directory and make it easy to manage content. Notion was a great choice then, so I opted for it.
While Notion worked pretty well for most cases, the setup process wasn’t quite as easy as I expected, especially the process of mapping Notion elements to DOM elements.
I ran into some challenges and limitations.
Based on the limitations, I had to come up with creative solutions:
const formatDate = (date: string) => {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
};
formatBlogData Function to match the Notion blocks to each content type.export function formatBlogData(page: PageData) {
const { properties = {}, id, cover } = page;
const { Summary, Title, Published, Tags } = properties;
const title = match(Title)
.with({ type: 'title' }, (field) => field.title[0].plain_text)
.otherwise(() => 'Untitled');
const publishedDate = match(Published)
.with({ type: 'date' }, (field) => field.date.start)
.otherwise(() => '2023-01-01');
return {
id,
title,
slug: slugify(title).toLowerCase(),
tags: match(Tags)
.with({ type: 'multi_select' }, (field) => field.multi_select)
.otherwise(() => []),
coverImage: match(cover)
.with({ type: 'external' }, (cover) => cover.external.url)
.with({ type: 'file' }, (cover) => cover.file.url)
.otherwise(() => ''),
publishedDate: {
raw: publishedDate,
formatted: formatDate(publishedDate),
},
summary: match(Summary)
.with({ type: 'rich_text' }, (field) => field.rich_text[0].plain_text)
.otherwise(() => 'Placeholder summary'),
};
}
These additional components and functions led to codebase complexity, and I soon realized there was a need for an alternative CMS that could offer scalability and maintainability over time.
Today I am excited to share my recent adventure of migrating my blog content from Notion to Directus—a headless content management system (CMS). You may wonder, why Directus?
Well, Directus is not just another CMS. It offers a unique combination of flexibility, customization, and collaboration for content management. It also has built-in user roles and permissions and a JavaScript SDK which were core features I was particularly interested in.
💡 If you're new to Directus, create a Directus Cloud account here.
Migrating my blog content to Directus was a smooth process. In my Directus instance, I initally created a blogs collection to hold all blog data. Then I went on to design the data model with the following fields:
id - Integer Inputstatus - Dropdowntitle - Inputcontent - WYSIWIGtags - Tagsauthors - Many to Many relationshipimage - Imagedate_published - DatetimeBased on the data model created, I transferred the blog content from Notion to Directus.
For seamless integration with Directus, I leveraged the power of the JavaScript SDK. The first step involved installing it using the command:
npm install @directus/sdk
Subsequently, I eliminated the Notion-specific helper functions and code that pertained to content block handling. In the index.js file, using the Directus SDK, I fetched all blog posts, simplifying data fetching from Directus.
export const getStaticProps = async () => {
await directus.auth.static(process.env.DIRECTUS_TOKEN);
const result = await directus.items('blogs').readByQuery({ sort: ['published_date'] });
const resolvedResult = await Promise.all(
result.data.map(async (blog) => {
const author = await directus.items('Authors').readOne(1);
return { ...blog, author: author?.name };
})
);
resolvedResult?.forEach((blog) => {
blog.image = `${process.env.DIRECTUS_URL}assets/${blog.image}`;
});
return {
props: {
blogs: resolvedResult,
},
};
};
By leveraging the WYSIWYG field in Directus, I could seamlessly handle the content of my blogs without the need for Notion components. Also, I no longer needed to rely on the formatDate function I had developed earlier, as Directus provides built-in sorting capabilities.
Directus proved to be a game-changer in terms of image handling. In Notion, images are stored as embedded assets. In Directus, images are stored as separate files. Each image in Directus is assigned a unique URL, facilitating seamless integration and effortless management.
Given that my project was built on Next.js, I made necessary modifications to the code, specifically targeting dynamic route data fetching. I also utilized the blog's ID as a slug for the blogs, eliminating the need for the Slugify library.
Here's the snippet showing the code used:
export const getStaticProps = async (context) => {
const blogId = context.params?.blogId;
const directus = new Directus(process.env.DIRECTUS_URL);
const blog = await directus.items('blogs').readOne(blogId);
blog.image = `${process.env.DIRECTUS_URL}assets/${blog.image}`;
return {
props: { blog },
};
};
With my blog content now securely hosted in Directus, I'm able to utilize more features of Directus and collaborate with other content creators and blog writers. I can:
This project has been exciting, and I hope you have gleaned insights from this migration.