Cloudflare Pages Migration Notes

4 minute read

Ever since I started the journey with static websites , I have always loved using the Jekyll or Hugo + GitLab Pages combo. A few examples include:

  • this site
  • Ubucon Europe Portugal - a static website for Ubucon Europe back in 2019;
  • adistancia.ansol.org - a static website listing open source software for schools and teachers to use during COVID-19 and lockdown times.
  • More private ones, granted.

The main reason I enjoy it is things work right out of the box, after the initial point & click instructions and it is effortless.

However, one major downside led me to rethink the approach and try a new product (to me, as it is not necessarily new): Cloudflare Pages. Cloudflare Pages is a JAMstack platform, where you can deploy your static websites (or add functionality using Functions, which resembles a lot AWS Lambda’s) and has one big features that matter to me: Redirects. With redirects, I can finally take down the ugly default link GitLab (and GitHub) Pages set for your domain: <repo>.gitlab.io or <repo>.github.io.

This post is about some notes on this migration to Cloudflare Pages, as my approach gives more control over the pipelines that deploy resources and uses other tools to upload files, instead of integrating Cloudflare with your VCS of choice.


Local Testing

The product’s main selling point is that you simply integrate your Cloudflare account with your Git platform (GitHub or GitLab) and you can automatically start deploying sites after things get merged in the “production branch”. I personally wanted to stay in control of all my pipelines, so I essentially re-did my repositories’ pipelines in GitLab to account
for this new approach. In a later chapter, I’ll cover what those stages look like.

In a nutshell, after building you site and generating all the static files it needs, you can use wrangler to deploy your site into your pages. First, however, create the application in the UI so you know where to deploy. Go to “Workers & Pages” > “Create application” and proceed from there. Beware the project name will automatically be granted a subdomain in *.pages.dev, which is pretty much like <repo>.gitlab/hub.io. This was quick to do, after configuring wrangler, you can simply run wrangler pages publish <OUTPUT_DIRECTORY> --branch=<default branch> and your website will be published to Cloudflare Pages. However, this raises some problems.

CSS Broken Paths

In my Hugo config, I am setting a baseUrl which suffixes all resources with this path. After the steps from above, the website was essentially broken and everything was misplaced - another thing I noticed is that I have not changed the DNS Records setup from GitLab (you essentially add GitLab’s nameservers on Cloudflare and a CNAME to GitLab) - so I went to my account’s DNS Records setup, removed all setup related to GitLab, then connected my Cloudflare Pages project with my own custom domain and site. It automatically creates a CNAME record for you (your custom domain pointing to Cloudflare’s infrastructure), with DNSSEC and a few other cool features. But this does not resolve the ugly paths - that was thanks to Bulk Redirects, which are, essentially, redirect rules that get applied to all accounts1.

I did one to redirect all requests to Cloudflare’s provided *.pages.dev domain to gsilvapt.me - and voilá, my assets were back!

GitLab CI Changes

As mentioned, I wanted to stay in control of my pipelines after this migration. Moving to point & click solutions was not appealing. With the following two stages, you can basically repeat what I described in the Local Testing and deploy your changes to your Pages site very quickly:

 1build:
 2  stage: build
 3  script: hugo
 4  artifacts:
 5    name: build
 6    paths:
 7    - public/
 8    expire_in: 1 day
 9
10deploy:
11  image: node:latest
12  stage: deploy
13  dependencies:
14  - build
15  script:
16  - npm install -g wrangler
17  - wrangler pages deploy public --project-name=gsilvapt-me --branch=main
18  rules:
19  - if: $CI_PIPELINE_SOURCE == 'push' && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH

The first stage, build, will have to build the static assets for the site. Using the artifacts](https://docs.gitlab.com/ee/ci/jobs/job_artifacts.html) functionality from GitLab, you can cache results without having to constantly building. It’s not that big of a site and it’s not that hours matter on the free tier, but hey… Optimization matters to me, okay? :)

The deploy stage uses node-based image to install wrangler and call the commands to deploy the project. You are probably wondering how auth is performed at this point and that is a good question. Wrangler supports environment variables for things like the account ID, zone ID and API token, which are all the keys you need to authenticate with the services. Thus, go to your repositories variable settings and add the following variables:

  • CF_ZONE_ID - so it knows which zone ID to deploy changes.
  • CLOUDFLARE_ACCOUNT_ID - same, but for the account.
  • CLOUDFLARE_API_TOKEN - this is a user-generated token for this purpose only. I used one of the templates for workers, with specific permissions to read/write with Workers and Pages.

A cool thing about GitLab Pipelines is that you can specify the rules in which the stage is added to the pipeline. For now, by default, I am only deploying to production resources that get merged into the default branch (main). However, Cloudflare Pages support different environments to publish changes, so it would be nice to use the merge request pipeline to deploy the resources in staging first, so I can view the changes, and only when merged things get deployed to production (which is something as simple as dynamically adding --branch=main when I want to publish to production). On the other hand, it might be total overkill, considering this is a personal site and there’s no reason to complicate that much.

Thank you for reading and hope this quick post helps you bootstrap your next Hugo + Cloudflare Pages site, without using their integration on GitHub/GitLab.

References

comments powered by Disqus