The Great Contentful Migration

Sat Oct 15 2022

In my last post I claimed that the blog project was complete, and I would be transitioning into something new. However, I realized I had some important work still outstanding. Going forward, I want joelj.ca to be relatively hands off -- cheap and easy to maintain. However, the current architecture requires me to patch and maintain servers, and manage SSL certificates. The self-managed Wordpress instance was always a make-work project, and with this whole experience completed and documented, I’m fine transitioning off to something more hands off.

To support these requirements, I decided to change my CMS from Wordpress to Contentful. Contentful is a headless CMS by design, which fits my requirements well, since my SPA is handling all the rendering. It’s also Software as a Service, which means no servers for me to manage. Even more, all my needs fit within the free tier, which totally eliminates one of my costs.

Migration of Wordpress Content to Contentful

In order to move to Contenful, I needed to migrate my existing content from Wordpress into my Contentful account. I wrote a script in JavaScript to perform the following migration steps:

  1. Read all my Wordpress posts and pages from the API

  2. Transform the content into a format that matches my target Contentful model

  3. Use Contentful’s management library to load the data into my account

One interesting technical challenge this migration posed is that Contentful uses a proprietary format for storing rich content (the type of content I want for posts and pages). Their libraries dynamically convert this format to HTML in the browser. This is in contrast to Wordpress, which stores rich content as raw HTML. So I had a conflict between my source data and target data. To get around this, I added two fields to my Contentful model: Content and Legacy Wordpress Content. Content is a rich text field supporting the proprietary format that I would use for future content. Legacy Wordpress Content is a plain text field, where I would load the raw HTML content from Wordpress. Then, my front end would use the legacy field where available for the old content, otherwise falling back to the rich text field for new content.

The Switchover

With the content loaded into my new CMS, I now needed my front end to consume from it. I also wanted my code to support both Wordpress and Contentful simultaneously, so that I could use a feature flag to manage the switchover. My existing Angular model was tightly coupled to the Wordpress API schema so the following refactorings were required:

  1. An Abstract Base Class (ABC) was introduced to act as the common interface for both content services. A platform-neutral data model was also added. b0f61b4f86d0d3b42da347131e3c5a053cc37bf5

  2. The existing Wordpress service was transitioned to the platform-neutral interface. e3bf7931b8d0df339238fb7508c2863bb29a21e0

  3. A new service was added for Contentful, implementing the same ABC (fully fleshed out in later commits). d4239c8e6e712dd7047f8341f303c30b5d9af10a

  4. A feature flag was implemented to choose which service gets used. 84e50351ca4774b50b1ed3c32aaf55ebbabd6c8a

Use of a feature flag enables the following Continuous Integration benefits:

  1. The Contentful service could be merged to trunk and deployed to production before it was even complete. This prevented a painful merge upon completion of the code.

  2. I could toggle the flag in my browser to test the changes in production, before making them available to the wider world.

  3. Once I was ready to release, if rollback was necessary, I could simply flip a configuration item.

Additional Architecture Changes

Apache previously served both Wordpress and my Angular SPA. To eliminate my VM altogether, I needed a new host for the Angular application. I chose Azure’s Platform as a Service option, Application Services, and implemented a very basic Express (Node.js) application to serve the content. Using PaaS means I don’t have to worry about the underlying OS and networking -- I just hand off my code to the cloud, and Azure figures out how to make it run. 

Since all my files are static, another option would have been to use a Blob Storage Container instead, served through the Azure Front Door CDN. However, having a proper Node.js server opens up the possibility of transitioning to Angular Universal in the future, which would enable Server Side Rendering. My attempt to get Angular Universal working failed, but I may return to this at a later time.

Future Plans

I think the blog is done for real this time. I’m sure I’ll do some minor patches here and there, but I want to commit my focus to the new project that’s taking shape. The new project, which I’m calling Strength Journal, will be a web application that takes the place of a workout log journal (currently, I use paper to record what lifts I did in the gym, and the weight I used). I’m envisioning a stack of SQL Server, ASP.NET Web API and Angular. SQL Server is the only thing I’m firm on since I need this to supplement some of the DBA learning I’m doing for work. I’ll be building out some prototypes and proofs of concept this week.