Why I Moved from Notion to Ghost for My Headless CMS
Tags: blogging, cms
Published: 2023-05-26 00:31:43

---
title: Why I Moved from Notion to Ghost for My Headless CMS
published: true
date: 2023-05-25 03:27:54 UTC
tags: blogging, cms
canonical_url: https://macarthur.me/posts/ghost-as-headless-cms
---

I've been dogfooding [JamComments](https://jamcomments.com/?ref=alex-macarthur) as I iterate on it, and as a part of that effort, I've committed to blogging a little more regularly. It works out because I like doing both of those things.

Along the way, I also decided to move away from using flat Markdown files for blog posts. I wanted something a little friendlier for "on-the-go" writing, and it was becoming a little too cumbersome to manage the publishing process via Git repository (there are solutions to this, but I'm not totally sold on any of them).

I like the authoring experience of Notion, and was intrigued by its relatively new API, so I took the plunge and decided to upgrade this statically rendered site with Notion as its CMS.

That transition was fine until until things started to hurt. Quickly enough, I began to realize Notion might not be the best platform on which to further scale this sort of content. In the end, I chose to make another switch – this time, to the open source version of [Ghost](https://ghost.org/?ref=alex-macarthur). I'll break it all down a bit in the form of several numbered lists.

## Issues w/ Notion

Notion's writing experience is very good, but in terms of using it as a headless CMS, there were a few key things I didn't want to tolerate anymore.

**#1. Build times were slow.** Last I checked, Notion's API doesn't support fetching all of a page's content at once. Instead, you're required to fetch a page's individual blocks and stitch the content together. That makes for a _lot_ of API requests and a lengthy build, compromising not only the development experience, but also the update process. Making the slightest tweak to a single post meant waiting for another wrinkle on my face to surface and a gray hair to sprout.

**#2. [API limits](https://developers.notion.com/reference/request-limits?ref=alex-macarthur) were frustrating.** The fact that API limits exist isn't a problem, but it sure becomes a bear when you need to spend a _lot_ of requests building a single page (see the previous point). I'd commonly find myself blocked from making more requests (especially during development), and would need to wait a few minutes to get going again. Technically, there are ways around it (the response will let you know how long you need to wait via `Retry-After` header), but doing so only further exacerbates the "slow build time" problem.

To avoid hitting limits in local development, I set up a local caching mechanism using [lowdb](https://github.com/typicode/lowdb?ref=alex-macarthur). It did the job, but it always felt like more complexity than I should need to manage for a personal blog, and I occassionally became annoyed when I needed to remember to blow away that cache when I wanted to see freshest content.

**#3. It's _too_ easy to publish.** There's no dedicated "save" or "publish" feature in Notion. When you make a change, it's "stuck," and totally eligible to be taken up with the next build of my site. I was constantly worried about accidentally tweaking the content, and later discovering I had butt-typed and sent an unwelcome change to my production site.

**#4. Handling images was too complicated.** I was initially excited about the fact that Notion's image URLs expire after some time and how I'd need to engineer some solution to make them more/less permanent. It was fun -- I used Cloudflare Workers and R2 to pull it off and even [wrote a post about it](https://www.macarthur.me/posts/serving-notion-presigned-images-with-cloudflare?ref=alex-macarthur). But in hindsight, it became another level of complexity I didn't really want to maintain or deal with later on. Plus, it contributed to the long build times.

**#5. I want webhooks.** At the time of writing, Notion doesn't offer any webhooks that fire when content is updated. I would have loved to use this for rebuilding my site whenever a post was added, updated, or deleted. Instead, I needed to rely on manually triggering a build myself as needed; or waiting for [the cron job](https://github.com/alexmacarthur/macarthur-me-astro/blob/master/.github/workflows/rebuild.yml?ref=alex-macarthur) I have set up to rebuild after some time. [I've seen people get creative](https://www.reddit.com/r/Notion/comments/nd76ec/notion_api_webhooks/?ref=alex-macarthur) to solve for this, but I wasn't willing to take on that challenge.

## Searching for a Headless Option

I'm cheap and [I really like my static site](https://www.macarthur.me/posts/boarded-the-ssg-train-again?ref=alex-macarthur), so I knew moving from Notion would mean looking for good, affordable (free), headless options. WordPress was a top contender. I have a solid amount of experience with the platform and felt I could knock it out pretty quickly. But a few things deterred me from taking that path. Another list:

**#1. Self-hosted WordPress doesn't support Markdown out of the box.** I'd need to research, vet, and install a separate plugin. I just didn't wanna deal.

**#2. There's no built-in membership/newsletter support.** I'd need to yet again go through the plugin vetting process, or else stick with something like Mailchimp, which I didn't love.

**#3. I'm a little burned out on WordPress.** There's nothing very tangible to this one. I just wanted something that I felt was a little more... focused, cruft-less, and removed from that ecosystem.

## Then Ghost Came Along

I've known about Ghost for several years and while I've always liked the vibe it emits, I had never tried it. But when I did, I was impressed. List time:

**#1. The writing experience is slickkk.** This is the thing that first got me hooked. The UI is modern, simple, clean, and intuitive. It supports Markdown-like syntax out of the box too, which made it real easy to get acclimated. I also got all of the blog goodies I previously took for granted (drafts, tags, featured images, etc.) – things I needed to cobble together with various fields in Notion. And there's also a dedicated "publish" button baked into the platform.

**#2. The REST API is fast and free of limits.** It also comes with JavaScript SDKs for both pulling content and managing admin-related things. I had no issues and was blown away by the results, especially coming from what I had become used to (no more stiching pages together with multipme requests!). Look at the build time difference my Notion-powered and Ghost-powered sites:

Keep in mind that the "slow" approach involved both pulling content from the API, as well as the fancy image handling I was doing with Cloudflare. Still, that's like nine times faster, and I get to shed a whole lot of complexity along the way. It was an easy sell.

**#3. It comes with built-in newsletter/subscription support.** To be honest, managing a newsletter and subscribers wasn’t even on my radar when I initially started looking at Ghost. But then I started to explore the feature and discover just how respected it is in the content world (many have migrated to it as an alternative to Substack). Mailchimp had always felt a little heavy-handed for me, and I liked the idea of managing both my content and subscribers in a single place. And once I toyed around with membership features of the REST API, it became pretty clear this was a serious alternative to what I had been using.

**#4. It's got webhooks.** And good ones! I can easily fine-tune when a new build is triggered, which is nice because it means it's not triggered whenever a meaningless event takes place (like when a draft is updated, for example). Here's a view into the hooks I have set up as of writing:

In all, it takes away all of the complexities I was getting sick of dealing with, and leaves me with features I actually care about.

## Here's a First

Usually, soon after making a pretty large shift like this, I start feeling a hint of malaise over how it's all put together. It's the curse of any developer – soon enough, it becomes incessant enough to knock it all down and rebuild it with a different foundation.

But this time feels different. My current stack provides pretty much everything I've consistently wanted. Astro gives me a performant static site with a great developer experience ([I've written about this](https://www.macarthur.me/posts/what-i-like-about-astro?ref=alex-macarthur)). Plausible gives lightweight, privacy-minded analytics ([this too](https://www.macarthur.me/posts/moving-from-google-analytics-to-plausible?ref=alex-macarthur)). And now Ghost gives me a hassle-free CMS that I really enjoy using (refresh the page to see what I've written about this).

I think I'll be leaving things where they're at for a while.