Lessons learned from “just” replacing a CMS

Last year, I began the process of replacing the content management system (CMS) that powers this blog. This turned out to be a year-long journey in which I rewrote most of the blog. Surprisingly, little changed visually, as the changes happened behind the scenes. This experience serves as a reminder that projects can take a long time when you are navigating uncertainties and trying to do too much at once.

It all started with the need for a CMS to efficiently write blog posts. With Netlify CMS, I had an out-of-the-box solution from the beginning, that allowed us to write and edit posts for the blog without having to run a full development setup locally. Netlify CMS works with markdown files that are stored in the Git repository along with your code. Changing a document commits the changes to your repository, where you can use the markdown files to generate your static website. This provides a great developer experience – but also a great editing experience. The WYSIWYG editor made editing easy and I could even write posts from my smartphone. Whenever I had an idea, I could just fire up the editor, make some notes, and come back later to turn it into a real post.

This was all fine until the moment Netlify CMS died. It lacked maintenance and I had more and more problems with Netlify CMS dependencies conflicting with our own dependencies. In the end I had to pull the plug – I removed the CMS from the blog early last year. From that point on, we had to edit all posts directly as markdown files. However, I would not blame this as the main reason for posting less often 😉.

Later, Netlify CMS was renamed to Decap CMS and found a new home and maintainers to take care of the project. But I liked the idea of trying out something new: This is where Tina comes in. Tina works with markdown files in a Git repository similar to Netlify CMS, but differs in providing a GraphQL based content API for querying the documents and live updates for visual editing. The backend is provided as a SaaS, but is open source, and they also have the option to host the backend as part of our blog – which is a nice escape hatch if the project is not active in the future. Also, the whole setup is more modern and the editor is less tightly coupled to our code.

So I went ahead and started the migration. Meanwhile, the new app router for Next.js had been released as part of a new major release, and it seemed like a much better fit for Tina. I had to migrate some of the routes to the app router first, but even though it was still in beta, it worked really well and turned out to be a good combination. After a while, it was time for the first test run of the new implementation. This blog is hosted on Netlify and we use the Next.js Runtime for Netlify for the deployment. It turned out that the runtime didn’t have proper support for the app router at the time, so the migration was held up for more than half a year. This was a long blocker, and I think I jumped on the bandwagon too early. Once the runtime was finally rewritten to support for the app router, the migration could continue.

But I also had other issues too, such as migrating from MDX to the limited Tina flavor of MDX. I needed to find more generic solutions for our MDX use cases that would also work with Tina. Instead of embedding JavaScript code directly into our Markdown, I decided to replace it with CodeSandbox embeddings. Our lightweight server-side syntax highlighting needed to be replaced with a progressive client-side solution. And some of our smaller Markdown transformations had to be ported to the Tina Rich Text AST.

I was quickly reaching the limits of Tina, such as the limited query capabilities of the content API. Previously we had our own API that parsed the markdown files directly and provided an interface that was designed to respond to all of our use cases. But the content API was not generic enough to meet all our needs, even simple things like our page-based navigation were difficult to implement without workarounds and multiple queries, as Tina uses a cursor-based approach to pagination instead. The backlog of things I needed to check grew and grew…

In the end, there were too many problems to solve all at once – and after so such a long wait, it was hard to continue with the project. I restarted the project a couple of weeks ago, switching from a big bang migration to several steps that could be released in stages: First, I tried to remove features from the blog that made the migration to Tina difficult, such as certain MDX features we used, and replace them with new solutions. Then I migrated the entire blog to the new app router and verified that it could be deployed on Netlify. In the meantime, I was even able to add new features, such as categorizing our posts with tags, that had long been blocked for a long time by the migration. As a final step, I migrated all the content to Tina, fortunately reusing most of the work from my first attempt. This allowed me to focus on one problem at a time and finally achieve my goal of migrating the blog to Tina 🎉.

So what did I learn? Would I do it again? Since it’s a private project, my goal is to learn and work with interesting technologies. Adopting a technology early can be challenging, but it can also be a great opportunity to learn things. Sure, working on a project for a year can be a bit demotivating, but achieving the goal in the end despite many uncertainties can be a great relief.

But I wouldn’t do that in a professional environment. It’s much more important to scope the work so that they it’s manageable and doesn’t involve too much uncertainty or risk. A company might not be able to operate a year without a CMS just because the development team wants to try out a new technology that isn’t ready yet. So I would probably go with a fully integrated solution that doesn’t require me to put the pieces together myself, so I can spend more time focusing on the company’s actual business. That may be boring, but it gives me a lot of confidence that I can do it!

Tags: blog, cms, react