Writing ·

Why I moved my blog's content out of Contentful and into the repo

Eduard Kyvenko · 4 min read

While I like GraphQL, Contentful was really an unnecessary step in my workflow on this website. So in September I moved the content out of Contentful and into the repository, next to the code, with Keystatic as the editor.

How I want to write

I want to focus on technical writing and help myself create more content. Markdown is my number one choice, and I'd like to customise my content while using the tools I'm most familiar with as a programmer.

That's why I've been experimenting with a content creation pipeline that allows me to write blog posts in a conversational format with LLMs, using voice transcription. I'm sceptical about LLMs as decision-drivers, but they also enable me to work faster and wider, so I'm embracing them. My focus stays on the human touch, and the tools help the content creation process instead of driving it or taking critical decisions for me.

OpenSuperWhisper, a local voice transcription tool, and dictation in Claude Code take my notes and convert them to text, so I can iterate on them quickly. I'm very busy with my work, so this needs to be time and cost efficient for me personally.

My tools evolve along with the process. One of them is a content writing skill for Claude Code that interviews me, so I can dig deep into a topic in the conversational format I actually prefer. It's a work in progress, and this post was written with it.

For this, I really needed the code and the content to reside in one place, to enable faster iteration loops and easier access for LLM tooling. Of course it could have been done with Contentful, but I really want to keep it slim and close to the code, so I chose a code-first approach.

Why I dropped Contentful

Contentful is a good product. It's very user friendly, and it uses GraphQL, which is easy to integrate. It has a CDN, it supports many content types and has a rich text editor. In general, it's very flexible for content creation.

One particular thing I didn't really like about Contentful's GraphQL API is that every field is nullable, or optional, which gets rather annoying. Even fields marked as required in the content model come through as possibly empty, so the code fills up with null checks for values that are always there. I just don't need required fields to be nullable.

A hosted CMS also needs plumbing around it: generating TypeScript types from the GraphQL schema, a preview mode for drafts, and revalidation so new posts show up.

Another concern for me is vendor lock-in before I have too much content. Before I start managing many different content types, I really wanted to make sure I needed a hosted CMS.

It's also much easier to iterate on the design of the website when the content is in the same place as the code. I can migrate it really quickly and change the format or the content types, without the migration risk of a third party being involved, such as a software-as-a-service headless CMS like Contentful.

Why Keystatic

If Markdown is my first choice, why not a folder of Markdown files? Because I also wanted a nice overview of all the content, and a structured model for the content with structured data fields. It's nice to have a UI for a quick overview, and a customisable UI that I can tweak and enrich with features I might need.

I was considering Decap CMS (formerly Netlify CMS) first. Decap is big and powerful, but it comes with complexity I don't need. Keystatic is a smaller, low-risk tool that I could migrate to in an evening. I hadn't used it before, but the content stays as plain files, so it would be easy to replace if I don't like it. It also provides a simpler rich text UI, without the unnecessary noise from Contentful.

My needs come first, features later. What I need is plain files and simplicity, and everything else can be added later if there's a need.

The content now lives as plain files in the repository, and Keystatic is a local UI on top of them. The types come straight from the content schema, and only the fields that can actually be empty, like images and dates, are optional. The plumbing is gone too. The code that fetches content shrank from 142 lines to 38, and nine dependencies went away. A draft is now just a file I haven't committed yet.

The admin UI is never deployed, and that's for a reason: I don't actually need it anywhere. I keep it just as a local tool for editing, and it's not something I intend to ever use on the live version of the site.

Keystatic doesn't support multilingual content creation yet. There's an open pull request that adds it, but it hasn't been merged. It's a bit of a concern, since the Danish version of this site is surely coming, but not a thing I can't fix in an evening.

Otherwise, I gave up what Contentful is good at, like a very user friendly editor. For my technical writing, I don't need it. Contentful's CDN is replaced by Vercel's CDN, where the site is hosted.

If you're considering a similar move, I'm happy to compare notes. Get in touch.