De-Press-ed: Migrating from WordPress to Cloudflare Pages
This website was previously powered by a self-hosted WordPress, running on a VPS. In front of that, sat Cloudflare and its APO product that helped to speed it up and reduce burden on the origin.
The origin server was an old-school, janky, LAMP stack. In spite of the rest of the world seeming having ditched LAMP (or its ilk) and performing a chain of moves from the hottest static site framework year-to-year, I took a certain joy in the legacy. I liked the fact I was dogfooding the experience of taking a weak origin and magically turning it into something that can operate at Internet scale with a few button clicks.
Lately though, there’s been a bit of bluster in the world of WordPress. I have little skin in that game, other than as an end-user of a software product that I have to run and maintain. That meant having posts on the matter thrust into my WordPress dashboard under the “Wordpress News and Events” panel. Sure, I can remove that panel, but it used to have some value. Abusing the panel to inundanate me with WordPress politics is not cool. And the more I’ve read, the more it seems like there are some very blurry lines between the WordPress open-source project, WordPress foundation, wordpress.org, wordpress.com, WPTavern.
This site is simple and has infrequent content updates. WordPress was really overkill for my needs. However, maintaining it did have some toil. wordpress.org decided to tell me in my dashboard that they had blocked some sites from their update servers. Am I next? Probably not. Yet, the fussing about I’ve seen on the Internet the past couple of weeks has given me the kick up the arse to finally ditch WordPress. I started the migration away from WordPress a few days ago, and in the meantime the situation has continued to escalate in absurdity. In the words of Blumhouse, its time to say NOPE, GET OUT.
Scouting the Migration Possibilities
There’s a world of opportunities when considering moving away from WordPress. I was already self-hosted, so this was a task concerned about migrating the WordPress site from one host to another. Some folks might be considering that and that’s a fine path. It’s something I’ve done in the past, moving from wordpress.com, to shared hosting provided by namecheap, to VPS in 2018. If this suites your needs, absolutely try it. There are a number of plugins, tools and tutorials that can help you.
For my purposes, I wanted to ditch the overheads of running a complicated origin server for dynamic content and content management, for what is effectively a simple site.
Over the past couple of years its been exciting to watch Cloudflare Pages progress by leaps and bounds. From the developer site:
Frontend developers want to build fast and beautiful sites, not play system integrator: bogged down by configuring build systems, setting up environments, and keeping production up to date.
With Pages, you can connect your GitHub or GitLab account. After that, it’s just git push — we’ll build and deploy for you.
GitHub and the IETF
This sounds exactly how I work on IETF standards documents! The source of the document is MArkdown and it is managed in GitHub repositories, allowing issue tracking and change proposal reviews. CI builds and generates documents. A live “editors copy” follows the leading edge is hosted on GitHub Pages. When we’re ready to cut a baseline version and publish it to the formal IETF Datatracker, we tag a commit and CI does all the work. For examples, check out the HTTP or QUIC Working Group repos 1.
First Attempt: Cloudflare Pages with Simply Static
So I’d decided I wanted to migrate my blog to Cloudflare Pages. A colleague had mentioned that they had used something called Simply Static. An Internet search quickly returned this tutorial - https://simplystatic.com/tutorials/cloudflare-pages-wordpress/ - that explains:
Simply Static is a WordPress plugin that lets you convert your dynamic WordPress site into a static website with just a click.
This plugin easily converts your WordPress theme, pages, posts, dynamic content like forms and comments, and more into their static version. This process is quick, so you won’t have to wait long for it to finish.
The tutorial does an excellent job of explaining all the necessary terms and guiding you to getting something up and running, including how to link Cloudflare Pages to a GitHub repo.
However, git repos are not the only way to get a static site up quickly. The Cloudflare documentation - https://developers.cloudflare.com/pages/how-to/deploy-a-wordpress-site/ - explains how to generate a ZIP file with Simply Static and simply upload it directly to the Cloudflare dashboard. I figured I’d try this out first to see if I liked the results. The upload took a couple of seconds and went live in a couple of minutes. You can see the result live at https://lp-ss-zip.pages.dev/, it’s pretty great!

It’s also easy to setup a custom name, I followed these instructions to put the test site on a subdomain - https://lp-ss-zip.lucaspardue.com. Under the hood this just uses CNAMEing.
Alas, while this approach was simple. I left me with HTML sources. I hate HTML. I like Markdown.
Its totally possible to have a workflow that keeps WordPress around as a CMS and authoring tool, and then use Simply Static (or some other tool) to export the website, and then pass that to Cloudflare Pages (or some other host). However, that didn’t feel satisfying for my needs. I wanted to drop WordPress and GUIs, and move to source code and devops.
All gain, no pain
I really enjoy a workflow of writing Markdown source and generating HTML, CSS, and JS. However, I loathe having to maintain tooling or infrastructure. Cloudflare Pages offers up the perfect features for me: I write the stuff and push it to a git repo, it deals with all the bullshit and then publishes my website near instantly. No infrastructure or tooling for me to maintain.
So the question was: how can I migrate my WordPress site into Markdown and get that to build as a website on Cloudflare’s infrastructure?
I did a small amount of research. There’s many options. I don’t profess to have the single best answer. I just landed on something that seemed to work for me and figured I’d write it up as some notes that others might find useful. I’m still learning and getting experience and expect I’ll change things up as time goes by.
The Real Migration
I landed upon the blog post How to Move from WordPress to Hugo by Terry Godier and found it resonated strongly with my motivations. More importantly, it was very clear on its instructions. I had heard the name Hugo but didn’t know much of anything to do with its specifics. Terry’s writing gave me confidence that this would be an approach that would yield a successful result, so much credit to them, thanks!
I followed a different order of steps than Terry presented, mainly because I know Markdown and not the other stuff. I also branched off at the point he talks about Netlify. YMMV depending on skills, experience, or choice of hosting provider.
Step 1: WordPress to Markdown
The official Hugo website lists a number of migration tools for various Content Managemet Systems (CMSs). There are 4 listed for WordPress. However, Terry’s blog suggested something different - wordpress-export-to-markdown by lonekorean (Will Boyd). I decided to follow this path. To quote Terry:
Simply install node into your environment, download a wordpress export (from wp-admin > tools > export) and run the script:
npx wordpress-export-to-markdown
The tool generates a folder called output by default, that contains the
markdown and images from the exported site. I played around with this a few
times to get a folder structure and naming scheme that I liked. There were many
other options that I didn’t really care to explore but in the fullness of time,
I’ll probably wish I did :)
Step 2: Bootstrap a Hugo website
I’ll admit I did a fair amount of trial and error here, as I was learning the ropes.
I haven’t noted it early but I decided to try this entire migration process on Windows, using WSL and VS Code.
Between the Hugo Quick Start and Terry’s instructions I did something like below:
apt install hugo
hugo new site my-site
cd my-site
git init
git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke themes/ananke
echo "theme = 'ananke'" >> hugo.toml
cd ..
cp -r output my-site/content
THese instructions use the Hugo tool to bootstrap a folder, install a recommend theme (ananke), and then put your website content in place. With that all done, the only thing left to do is build and test the site.
hugo server
Unfortunately, I’d ignored all the advice about checking the Hugo version and basically the build failed because the theme was incompatible. I then made the stark realization that my WSL distro was Ubuntu 18.04. Oops. I spent a couple of hours doing some updates in the background while I explored the Markdown and read documentation on Hugo.
Once I was done with updates, I ran hugo server again and had a nice website
running on http://localhost:1313
Step 3: Cleaning Markdown and Styling
Markdown is pretty simple but there’s many flavors and extensions and whatnot. The export tool had done some weird things with figures, and some of the links, navigation, and metadata wasn’t quite to my taste. I spent a couple of hours doing some housekeeping and reading about/playing with shortcodes.
It turns out that having a CMS do things for you hides certain details. There were multiple copies of images with slightly different names. There were also some things that didn’t make sense anymore. For example, previously I’d made use of WordPress’s ability for people to leave comments on posts, or to fill out a contact form. By extensions, I’d used a plugin that generates a privacy policy and could do cookie banners etc. On the old site, this was pointless cruft. Comments were almost always spam, and needed a plugin to manage that. Plugins all the way down!.
I don’t want or need any of that crap on the new site. So it went in th dustbin.
I played around locally and iterated a bit on format and linkage across the site. There may still be things that are broken. I didn’t spend much time tuning the default theme, there seems to be a lot more things I can do to add features or make it pretty. All in good time.
Step 4: Pushing it to GitHub
Running hugo server performs a build. So the site folder was full of generated
content and other stuff you don’t want in a git source repo. The .gitignore I
ended up with was
public/*
resources/*
I used the GitHub CLI tool - gh - to create a new
private repo. Then pushed everything up to it.
Step 5: Create a new Cloudflare Pages Application and link it to Git
I followed the Cloudflare dev docs on how to deploy a Hugo site but skipped to the part about creating and linking an Application since I already had everything I needed setup.
The UI presents a Wizard for setting things up. I:
- picked a name
- chose the “Connect to Git” option
- followed the permissions granting steps to let Cloudflare read the repo
- then configured the builder with
- Production branch - main
- Build command - hugo
- Build directoty - public
I committed these choices and was taken to a new page showing me that Cloudflare was pulling my code and going build then deploy it!
I watched excitedly while the first few steps succeeded. Then got deflated when it told me the build had failed. Fortunately, the error message was recognizable, the Hugo version was too old :D
This was easily fixed by following the instructions for explicitly picking a
Hugo
version.
All it took was to set the enter the Application Settings and set the
HUGO_VERSION environment variable.
A new build was triggered, and boom, my website was live on https://lp-website.pages.dev.
Step 6: Vanity without CNAME
As I mentioned before, these
instructions
describe how to use a different domain that doesn’t have the pages.dev suffix.
However, this website does a number of things, not just the blog, all off the
apex lucaspardue.com. For example, I have a Cloudflare Worker that I use for
protocol debugging that sleeps for an arbitrary time and then echoes details of
the request and response. It lives at https://lucaspardue.com/delay/500 and
generates an output like
Welcome to Delay! You were delayed by 500ms. You are using the protocol: HTTP/3
with ciphersuite: AEAD-AES128-GCM-SHA256. request.cf.requestPriority: [empty
string]. Request body: null. headers: accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
accept-encoding: gzip, br accept-language: en-GB,en;q=0.5 cf-connecting-ip:
<redacted> cf-ipcountry: GB cf-ray: <redacted> cf-visitor: {"scheme":"https"}
connection: Keep-Alive dnt: 1 host: lucaspardue.com priority: u=0, i
sec-fetch-dest: document sec-fetch-mode: navigate sec-fetch-site: none
sec-fetch-user: ?1 sec-gpc: 1 speedwp_tiered_cache: true
upgrade-insecure-requests: 1 user-agent: Mozilla/5.0 (Windows NT 10.0; Win64;
x64; rv:131.0) Gecko/20100101 Firefox/131.0 x-forwarded-for: <redacted>
x-forwarded-proto: https x-real-ip: <redacted>>
I have a lot of Workers
Routes
that let me do cool things like this. I was concerned that CNAMEing
lucaspardue.com to lp-website.pages.dev would break them. So instead, I
setup a new route under lucaspardue.com/* that invoked the following worker
based on this example.
export default {
async fetch(request) {
/**
* An object with different URLs to fetch
* @param {Object} ORIGINS
*/
const ORIGINS = {
"lucaspardue.com": "lp-website.pages.dev",
};
const url = new URL(request.url);
// Check if incoming hostname is a key in the ORIGINS object
if (url.hostname in ORIGINS) {
const target = ORIGINS[url.hostname];
url.hostname = target;
// If it is, proxy request to that third party origin
return fetch(url.toString(), request);
}
// Otherwise, process request as normal
return fetch(request);
},
};
And with that, I was live!
Conclusion
I’ve used WordPress lightly for a number of years. Its been very flexible, allowing me to create blogs and other types of websites, some of which are managed by non-technical users. I’d not paid much attention to the open source project or the world of WordPress during that time. I’ve seen some things in the last few weeks that make me concerned about the future direction of the project and the impact that will have on the ecosystem. That’s creating too much risk for me, with respect to security updates, forks, or other shenanigans. I’m not a bargaining chip to be used in some “war” amongst tech bros that like to use terms like “going nuclear”. Nor do I want to be caught with my pants down when the shit hits the fan.
Moving off WordPress has not been that difficult a process. Nor did it take very long. I’m now in better control of my content with a workfow that aligns well with my day-to-day practices. I’m a lot happier with the fact that I can probably port to a different build system, framework, hosting provider, with short thrift.