Skip to main contentBuilding a Headless CMS with Notion + Gatsby: Why I Chose This Stack | John Click | John Clicks - Portfolio

Building a Headless CMS with Notion + Gatsby: Why I Chose This Stack

How I turned my Notion workspace into a multi-site publishing engine — and what I learned from doing it at Project N95 with Contentful first.

When I was Chief of Staff at Project N95, I helped support a headless React storefront backed by Contentful as the CMS. That experience taught me the power of decoupling content management from presentation — and the pain of paying $300/month for a CMS when you already have a perfectly good knowledge management tool.

Notion is that tool for me. I use it every day for everything: project tracking, prompt engineering, storing articles, managing my AI workflows. So when it came time to rebuild my personal websites, the question wasn't whether to use Notion as a CMS — it was how.

The Multi-Site Challenge

I have three domains, each with a different audience and visual language:

  • JohnClicks.com — Portfolio, resume, photography. Job-application ready.
  • JohnClick.dev — Developer blog. TILs, code snippets, technical articles.
  • JohnClick.ai — AI portfolio showcase. Westworld-inspired model evaluations.

All three pull from the same Notion backend. A single Blog Posts database has a Publish To relation that targets one or more website records. When Gatsby builds JohnClick.ai, it queries the Blog Posts DB and filters to only posts where Publish To includes the .ai site record.

How Gatsby Reads Notion

Gatsby is a static site generator. It runs at build time, not at runtime.

  1. You edit content in Notion (even from your phone)
  2. A build is triggered (push to GitHub, or eventually a webhook)
  3. Gatsby's sourceNodes API queries the Notion API
  4. Content is transformed into GraphQL nodes
  5. React components consume the GraphQL data
  6. Gatsby outputs pure static HTML/CSS/JS
  7. Netlify serves the static files

The deployed website has zero runtime connection to Notion. No API keys in the browser. No fetch calls from the frontend. The security posture is identical to a pure HTML site — because that's what it is.

What I Learned from Project N95

Before Notion was my CMS, Contentful was. And before Contentful, Airtable was doing double duty as both our operational database and our content backend — which is eerily similar to what I'm doing with Notion now.

At Project N95 I was Chief of Staff, but in a 400-volunteer nonprofit built during a pandemic, titles are suggestions. I ended up managing our entire Airtable infrastructure — candidate management, volunteer onboarding, supplier vetting, PPE inventory — and critically, Airtable also served as the backend for our React storefront built on Gatsby.

The architecture was almost identical to what I run today:

  1. Content lived in Airtable (product listings, supplier data, marketplace inventory)
  2. Gatsby queried Airtable at build time via GraphQL source plugins
  3. React components consumed the GraphQL nodes
  4. The built site deployed as pure static HTML — zero runtime API calls
  5. Netlify served the static files

Does that look familiar? It should. Replace "Airtable" with "Notion" and you have my current stack.

The Contentful layer came later — when we needed richer content management for marketing pages and blog posts, we added Contentful as a dedicated CMS alongside Airtable. That worked, but it was $300/month for what amounted to a fancy markdown editor with an API. And now I had two content backends that Gatsby needed to source from at build time.

The lesson I took from all of this: the best CMS is the tool you already live in. At Project N95, we lived in Airtable — it was our operational brain. Making it also serve content was natural because the data was already there. For my personal sites, I live in Notion. Making it serve content is natural for the same reason.

The difference is that Notion is doing something Airtable never did for us at N95: it's not just a content pipeline for websites. It's also a context pipeline for AI agents. But that's a whole separate post.

What's Different from a Traditional CMS

The thing that makes this setup unusual isn't the Gatsby part — lots of people use Gatsby. It's that the CMS isn't a CMS. Notion has no concept of "publishing" or "drafts" or "content types" in the way WordPress or Contentful do. I had to build all of that in the database schema:

  • Status (Draft → In Review → Published → Archived) is a Notion status property
  • Publish To is a relation to a Websites database — this is how one post targets multiple sites
  • Slug is a text property I set manually (or my agents set for me)
  • Content Type (Article, TIL, Snippet, Tutorial) controls visual treatment per site
  • Topic Tags are multi-select for filtering and categorization

Gatsby's source plugin reads all of this at build time and only generates pages for records where Status = Published and Publish To includes the current site. Everything else is invisible to the build.

This means I can have a blog post in Draft status, visible in Notion, editable from my phone, and it doesn't appear on any site until I flip the status. No deploy needed to create content — only to publish it.

Why Gatsby Over Next.js

I get asked this a lot. Next.js is the obvious choice for most React projects in 2026. But for my use case, Gatsby's static-first model is exactly right:

  • Zero runtime costs. Static HTML on Netlify's free tier. No server, no serverless functions, no cold starts.
  • Security posture. No API keys in the browser. No server to compromise. The attack surface is identical to a static HTML site from 1998.
  • Build-time data sourcing. Gatsby's sourceNodes API is designed for exactly this pattern — pull from any backend at build time, transform into GraphQL, consume in React. It's what we used at Project N95 with Airtable and it works the same way with Notion.
  • Multi-source composition. I can source from Notion, from local markdown files, from the filesystem — all in the same build, all unified into one GraphQL schema.

The tradeoff is that content updates require a rebuild. But for personal blogs that update a few times a week, not a few times a minute, that's fine. And when I eventually add a webhook from Notion to trigger Netlify rebuilds, it'll be near-instant anyway.

What's Next

The "Context Pipeline" post I mentioned above is coming — how the same Notion workspace that serves blog content to Gatsby also serves operational context to every AI agent I run. Same databases, dual consumers, zero duplication.

I'm also working on the image pipeline (getting NLM-generated slide decks and diagram assets to flow through Gatsby's build). And the three-site theme system — how JohnClicks.com, JohnClick.dev, and JohnClick.ai each have distinct visual languages while sharing the same backend.

But those are future posts. For now: Notion + Gatsby + Netlify. It works. It costs $0/month in hosting. And it lets me write from my phone and publish to three sites from one database.

lmk if you've built something similar — I'd love to compare stacks.


Subscribe on Substack for new posts.