Migrating from GitHub Pages to Cloudflare Pages
I have recently moved this Jekyll blog from GitHub Pages to Cloudflare Pages. Here is how I prepared a smooth migration (trying not to break things along the way).
# Post outline
- Cloudflare pages: what, why
- The easy steps: configuration, test, the docs
- Managed access
- The tricky bits: a smooth transition
- Redirect blog posts
- Dealing with the RSS feed
- Have we missed anything?
- The delta:
- Relative URLs in RSS feed (?)
- The light at the end of the tunnel:
- Support for rollbacks, redirects, custom headers, workers
# Cloudflare Pages
I like GitHub Pages! They make it easy to spin up Jekyll websites. GitHub Actions, then, provide additional space for customization, if one needs to. But simplicity comes with constraints, too.
In my case, I wanted to add privacy-preserving (and GDPR-compliant) analytics to the blog. GitHub doesnât provide analytics for pages. Plausible would be a good alternative, but the little traffic on this blog doesnât justify their paid plans; the self-hosted option instead requires spinning up an instance, securing it, maintaining it, and so on. Not ideal.
Thatâs when I thought about Cloudflare Pages â their option to serve static content.
Being integrated into Cloudflare, Pages can measure privacy-preserving analytics that donât rely on client state and donât require adding the most-hated-banner of the web: âAllow Cookies?â. Also, I have used Cloudflare to manage DNSs before. If I ever move this to a custom domain, it would be good to have everything it one place1.
So, Cloudflare Pages.
# Setting things up
Setting things up was smooth. Their
docs
will guide you to create the Pages project, provide access to the GitHub
repository2, and test the deployment the
*.pages.dev
domain assigned to the project. So far, so good.
Any trouble? When setting up the deployment, I experimented with their âAccess Policyâ, that gates who can see pre-deployed versions of the website. Thing is, once enabled thereâs no way to disable it through the interface without onboarding to their âZero Trustâ product. Luckily, we can go the API-based approach.
Having disabled âAccess Policyâ and tested the deployment we now have two
versions of the website, respectively served by GitHub and Cloudflare, on two
different domains (aldur.github.io
and aldur.pages.dev
).
How do we migrate users from the old domain to the new one?
# Preparing
Letâs establish our migration goals:
- We want links pointing to existing resources on GitHub to redirect to the same resource, on Cloudflare. No generic redirects to the root of the new domain that leave the users to find again their destination.
- We also want previous users to remain up-to-date. For that, we need to tell âbrowser usersâ that the blog has moved and notify somehow RSS users too.
I talk about users as if there is a crowd reading this blog. I should probably be using a singular form:
Hello mom! đ
What constraints do we have?
- The ideal solution would be responding with a
302
status code. But GitHub doesnât provide dynamic HTTP redirects. - Weâll have to engineer this through what we have: static files, HTML and JavaScript (this one if we really need to, as that breaks bots, SEO, etc.).
OK, whatâs the plan then? We will take the matter into our hands!
# HTML pages
All HTML pages at GitHub will redirect to their corresponding page at Cloudflare
by using <meta http-equiv="refresh"
content="3;url=https://aldur.pages.dev/[destination]">
and <link
rel="canonical" href="https://aldur.pages.dev/[destination]">
. This informs
search-engine bots of the redirect and lets us display an informative page to
the user. There are less than a dozen pages in total. It takes less to write the
HTML code by hand than to script it â but a Jekyll plugin could do that
automatically.
What the result looks like for our about page â try clicking on the link yourself.
# The RSS feed
The RSS feed needs special treatment. If someone consumes this blog only through the feed, theyâll need to manually edit the feed URI to get new posts, and we want to notify them. We will create a static new RSS entry that points to the new website.
The RSS feed entry showing the notification.
This handy website lets you validate the static RSS feed to prevent mistakes.
# Anything else?
For sure! Static assets, uploads, and so on. Short of continuing to serve their
original version, thereâs not much we can do. They are not HTML files, and we
canât redirect a webp
image without dynamic HTTP redirects. Still, thereâs
little chance of someone visiting/requesting static assets from the old domain.
This time is OK to compromise. We can use a little JavaScript to:
- Inform the user of whatâs happening.
- Do our best to redirect them to the right place.
Bonus: If we use a 404.html
file it will also catch and redirect requests to
resources that didnât exist on the old website â or anything we missed during
the migration. The downside, if you are asking, is that the JavaScript approach
doesnât work with curl
, bots, and so on.
You can try the resulting feel here.
The redirection plan completed.
# Green-light: Migrate!
With all things ready, we can reconfigure our GitHub repository to deploy GitHub
Pages from a specific branch (redirect_to_cloudflare
in my case). After
pushing our static code there, traffic will migrate to Cloudflare.
The source code is here.
It is also a good idea to stop ourselves from deleting the branch by mistake: GitHub allows setting branch protection rules and denoting branches as read-only. That should do it.
If you are reading this, it means the migration worked đ.
# Did we break anything?
So far, I only spotted one difference. Cloudflareâs RSS feed doesnât include the
full URI (including the pages.dev
domain), but only relative URLs. Thatâs
because the jekyll-feed
plugin relies
on site.url
or site.github.url
(if deployed on GitHub) â both missing in my
case.
Our RSS validator complains â and rightly so:
id must be a full and valid URL: /feed.xml
Interestingly, this doesnât break my RSS reader.
To fix this we
add a Cloudflare-specific configuration file and instruct Cloudflare Pages
to use it while building the site (jekyll build --config
_config.yml,cloudflare_pages._config.yml
in âBuild configurationâ).
This fix works for the production-version of the website. Unfortunately, Cloudflare doesnât let specifying different build configs for production and preview.
After applying the fix, the RSS feeds of deployment previews will point to the production URL, instead of the preview. Leaving the issue unfixed would not introduce this issue, because the relative URL would resolve to the correct domain.
# Wrapping up
Migrations of live environments need careful handling. They are usually not hard, but can get complicated.
In this case, the constraints of GitHub pages also required some hacks
innovative solutions. But also the risk was low â because of the very little
legacy we have.
Looking back, the outcome was good. We now have analytics (and rollbacks!) in place, the UX of the migration feels good enough, and thereâs room for more goodies and growth if the occasion comes (e.g., a custom domain or the addition of web workers). Also, if the need for a migration arises again, Cloudflare will provide better flexibility: HTTP redirects and headers.
# Footnotes
-
There are downsides too, of course, as this centralizes the Web and adds a âsingle point of censorshipâ. But thatâs something for another post. ↩
-
By default, Cloudflare integrates with GitHub to automatically deploy new versions on push to the repository (even on non-production branches to test changes).
I find it nice, but 1. Cloudflare needs to know how to build the website; and 2. has full access to the sources.
None of these points is an issue for me (the source is public, after all, and Jekyll builds are well-supported). But on the first sign of trouble, I would switch to pushing the assets to Cloudflare through GitHub actions for more control. ↩