Migrating from Prismic to Notion with Gatsby

Blog.
gatsby
cms
February 2023

Time flies and it really shocks me that the migration from nothing to using Prismic as cms for this very site was (already!) 1.5 year ago.

What is Prismic

Prismic provides a web interface to add/update/delete data, plainly speaking. And the data can be retrieved via API which comes very handy if you are building a website with tons of content, but want to separate concerns between data and the presentations. It is said to be a headless CMS, where “head” means the presentation layer (html/css/page templates). Unlike traditional CMS, such as Wordpress, which is basically a bundle of everything in one place.
Services offering similar services in the market includes: DatoCMS (which I really like the interface!), microCMS (made in Japan), Contentful, Strapi - many many many more.
So why can’t just interact with your own database by writing your own API? Yes, you can always deploy a MongoDb and use something like keystonejs to comprehend the cms pipeline. But most of the time there is no need to overshoot for simple use cases, for example, when you are building a campaign website, which has tons of content, but you won’t have time and effort to maintain anything other than the fancy interactions required. Services like this save time! Further, there are solutions in this genre that don’t even require a “database”, eg forestry.io.
Speaking from a free plan perspective, Prismic has been perfect. It has an easy-to-use interface, all pictures uploaded have built in CDN support, unlimited documents, and unlimited locales. That’s pretty much more than enough for most websites or websites with lots of content.

Problem with Prismic

Prismic is great for handling small to mid-size projects, and you can always load up the account by upgrading to higher-end plans. For this website, the blog was originally integrated with Prismic. Webhook is available so whenever new contents are published, the build process of this Gatsby site will be triggered and a new set of static pages will be generated.
My problem is, I don’t usually write with Prismic 😩. Usually, I simply copy and paste the written stuff to Prismic for them to be published. It just wasn’t part of the pipeline of how I usually write.
With the objective to write more in 2023 (about everything), I have the urge to switch to a cms that fits in more with my writing flow.

Candidates

DatoCMS

Pro: Very neat interface, brings joy to writing. Amazing locale support.
🚫
Cons: Only 300 records for free plan. Not very willing to invest to something that’d probably be locking later on.

microCMS

Pro: Easy to use.
🚫
Cons: no locale options.

Notion

This final candidate actually is not a pure headless CMS but it can actually be one. You can create integrations with the API provided.
Pro: Already a heavy notion user. More like only after tidying already existed contents I can happily write as usual.
🚫
Cons: API limit but for this use case, rarely.

Creating a Gatsby Source Plugin for Notion integration

Gatsby is a happy place with a huge community that provide more plugins than you need. So naturally a Notion Plugin for Gatsby should be there already. It is more like a WIP project so I decided to write my own.
The Gatsby Documentation on how to create a source plugin offers a step-by-step guide, and even a starter template. The basic structure of a Gatsby project with local plugin:
/site-root - gatsby-node.js <- local project node creation - /src - /plugins - /custom-plugin - package.json - gatsby-node.js <- plugin-specific node creation
There’s a bunch of APIs that can be consumed in gatsby-node.js to interact with the build lifecycle of the Gatsby site. The ones I used mostly in the integration:

createPages

For example, to create a page /blog/my-new-blog-entry
exports.createPages = async ({ actions }) => { const { createPage } = actions; createPage({ path: "/blog/my-new-blog-entry", component: path.resolve(/** path to page template **/.js), context: { /** page context to be serialised **/ id: "unique-random-blog-id" } }) }

sourceNodes

An API called during Gatsby bootstrap sequence. At this lifecycle point, this is where nodes can be created if specified. For example, to create a graphql node type NotionBlog
createNode({ id: 'unique-random-blog-id', // later can be retrieved by getNodeById() internal: { type: "NotionBlog", contentDigest: createContentDigest(...data), }, parent: null, children: [], ...anyAttributes, data: { title: "Hello World!", is_draft: true, ...etc } })
This action will create a node that is later queryable in graphql like:
query BlogQuery (id: {eq: $id}) { notionBlog { id data { title is_draft } } }
query BlogQuery { allNotionBlog { nodes { id data { title is_draft } } } }
It’ll automatically create 2 queries (notionBlogand allNotionBlog ) by camel-casing the node type. Also, it will generate schema with the default values provided if not specified otherwise, also camel-cased. So in this case, something like
type NotionBlog implements Node { id: ID! data: NotionBlogData } type NotionBlogData { title: String is_draft: Boolean }
Absolutely brilliant right!

createSchemaCustomization

By default, if you don’t specify any schema, gatsby will take whatever data you passed on while creating nodes to create a schema. Extending from the NotionBlog type above, if the source data includes array of objects:
createNode({ ... data: { title: "Hello World!", is_draft: true, content: [{ type: "paragraph", content: "Once Upon a time..."}], ...etc } })
Here, the generated schema would be
type NotionBlog implements Node { id: ID! data: NotionBlogData } type NotionBlogData { title: String is_draft: Boolean content: [NotionBlogDataContent] } type NotionBlogDataContent { type: String content: String }
The type name NotionBlogDataContent is acceptable but let say if it is a common type (shared between other notion pages) eg. NotionContent. When the API createSchemaCustomization get called, there’s an action createTypes available.
exports.createSchemaCustomization = ({ actions }) => { const { createTypes } = actions; const typeDefs = ` type NotionBlogData { content: [NotionContent] } type NotionContent { type: String content: String } `; createTypes(typeDefs); };
There is no need to specify existing type definition. This is a very very very useful API to sort out the tangled up schema from your custom data source.

More/Next to write about

  • createResolvers
  • Pipeline from notion → gatsby → vercel
Latest
Built with Gatsby ^5.3 + Notion Email 📥 Twitter 💬 Github 👩‍💻 LinkedIn