Virtualization Is Life!
14 Years on WordPress. Done in an Afternoon.

14 Years on WordPress. Done in an Afternoon.

If you’ve been following this blog for any length of time you’ll know the posting cadence has slowed dramatically over the past few years. Life, work, and frankly a lack of motivation to fight with the tooling have all played a part. But one thing that has sat in the back of my mind for a long time now is getting this blog off its aging WordPress LAMP stack and onto something modern, fast, and essentially maintenance-free.

I first seriously looked at this back in 2019. Then again in 2021. Every time I dug into it, the scope of what was involved sent me back to the drawing board. 808 published posts dating back to 2012, a media library sitting at 2.6GB, 14 years of inbound links and Google ranking I absolutely could not afford to break, categories, tags, RSS subscribers, comments… the list of things that could go wrong was long. So it kept getting pushed to the “someday” pile.

That changed last weekend. I knocked the whole thing out in an afternoon using Claude Code… and it was genuinely one of the more impressive technical experiences I’ve had in a long time.

The state of the server

Before we get into the how, it’s worth understanding just how bad the “before” was. The LAMP server running this blog was on Ubuntu 14.04 with PHP 5.6. Ubuntu 14.04 reached end-of-life in April 2019. PHP 5.6 went EOL in December 2018. That’s a server that has been running unpatched for the better part of six years hosting a public website. Not great. Getting this moved was well and truly overdue from every angle you look at it.

What I wanted

The target was Astro on Cloudflare Pages. Astro because it’s purpose-built for content-heavy static sites and has excellent Markdown support. Cloudflare because I’m already using them for DNS, there’s zero egress fees on their CDN, and the whole stack runs on the free tier at the traffic levels this blog sees.

Going in there were a few things that were simply not up for negotiation. Every single URL had to stay exactly the same. No dates in paths, no category prefixes. Fourteen years of inbound links and SEO ranking are not something you mess with. Images needed a proper home since 422MB of originals can’t just live in a git repo. RSS had to keep working because feed readers don’t forgive broken endpoints. And comments… I had 712 of them sitting in the database that needed figuring out.

Where Claude Code came in

I described what I wanted, Claude Code figured out how to do it, wrote the scripts, ran them, debugged when things broke, and kept moving. The WP-CLI export quirks, a WXR-to-Markdown converter, the Cloudflare R2 setup, wiring up a Pages Function… stuff that would have taken me a week of evenings happened in a few hours of back and forth.

That’s not me saying I sat back and watched. There was a fair bit of hand-holding involved. Decisions needed making. Some of the debugging required understanding the actual problem before a fix was possible. But the execution of those decisions was handled almost entirely by the tooling.

A few specific things that would have taken me hours alone:

The WP-CLI export. The WordPress admin export UI chokes on large sites. WP-CLI is the right tool but there’s a gotcha where it creates two files when you filter by post type, and if you don’t use {n} in the filename format, the second file silently overwrites the first. You end up with 316 media attachments and zero actual posts. Claude caught this, understood why, fixed it.

The HTML-to-Markdown conversion. 808 posts worth of WordPress HTML with TinyMCE editor artifacts, smart quotes encoded in Windows-1252 that became  characters, and the occasional [gallery] shortcode. A Python script that handled all of this cleanly would have taken me most of a day to write and debug. It took about ten minutes to iterate to something that produced clean output.

The R2 image setup. Cloudflare R2 is S3-compatible with zero egress fees. Getting 2,536 image files from the LAMP server into R2 and then wiring up a Cloudflare Pages Function to proxy /images/* requests is not complicated… but it’s also not something you just know off the top of your head. The rclone configuration, the Pages Function code, the bucket binding were all done without me having to look anything up.

Where I still had to drive

It wasn’t all plain sailing. The Cloudflare Workers/Pages merger is genuinely confusing right now and their auto-configuration kept injecting an SSR adapter that switched Astro from static to server-rendered mode. That took a couple of failed deployments and some actual understanding of what was happening under the hood before the fix was obvious.

The comments question was also mine to answer. 712 approved comments across 299 posts… I decided to archive them as static read-only HTML rather than drag in a third-party platform. Not the most exciting outcome for people who left comments over the years but it was the right call for a site that doesn’t need another dependency.

The tone of this blog. The design direction. The navigation. Those are still mine.

The numbers

The end result after one afternoon:

  • 808 posts migrated with all URLs preserved
  • 1,787 URLs in the sitemap (the new site adds category and tag archive pages that didn’t exist before, which is a genuine SEO improvement)
  • 422MB of original images in Cloudflare R2, served via CDN
  • 712 comments archived as static HTML
  • 13 seconds to build 1,850 pages
  • $0/month hosting cost on Cloudflare’s free tier

The LAMP server is still running but no longer serving any traffic. I’ll shut it down properly once I’ve run the new site for a few weeks and I’m satisfied nothing’s been missed.

What this actually represents

For me this is less about the blog and more about what this kind of AI-assisted automation now makes possible. Tasks that previously sat in the “too hard, too time consuming, too risky to get wrong” basket are increasingly within reach for a single person in an afternoon. I’ve been wanting to make this platform shift for years… the tooling just wasn’t there to make it practical. Now it is.

That’s a pretty significant shift in what’s achievable and I think we’re only just starting to see what that means across the board. If you’ve got a similar migration sitting in your own backlog… it might be worth dusting it off.

#Astro #Cloudflare #WordPress #AI #ClaudeCode