Cloudflare Pages PoC

This article describes my Cloudflare Pages PoC Journey.

Note
This PoC is an ongoing WIP, but here’s what I have so far…

Basic Production Site

  1. Checkout the CF docs

    Follow Cloudflare’s Deploy a Hugo site docs.

    I’m going to be using the CodeIT theme, so I’m using that where the docs use themes/terminal. Similarly, my config is set up as seen in my repo.

  2. Set a custom DNS entry

    Set a Custom Domain to allow easier navigation to your site. This appears to be able to be set in the Project’s “Custom domains” setting. if you look at your DNS settings afterwards you’ll see something like subdomain poc as a CNAME to cloudflarepagespoc.pages.dev.

  3. Initial build settings

    • Build command: hugo
    • Build output directory: /public
    • Root directory: /

    Build dependencies versions

    Note
    Default versions for build dependencies may well be quite old.
    

    There are separate environment variables for Production and Preview builds, so always try out new versions in a Preview build (see below) before upgrading the Production ENV.

    Either way, environment variables should be set to match your local development environment where possible.

    Go to your Pages Project page > Settings > Environment Variables

    Add the following, for example:

    Variable nameValue
    GO_VERSION1.17
    HUGO_VERSION0.88.1
    HUGO_ENVIRONMENTproduction

    Note: HUGO_ENVIRONMENT is used by Hugo as a selector for its configuration and can also be picked up by any additional code you may use. This repo’s config directory, for example, has two such options ‘production’ and ‘development’

  4. Deploy

    Deployment is simply a matter of pushing your local commits to the repo. The default branch for production builds is main so commit or merge to that and Cloudflare should initiate a build and deploy to your production site!

    The build can be followed by clicking on View Build and following the Build log, for example:

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    
    13:12:51.819 Initializing build environment. This may take up to a few minutes to complete
    13:15:27.988 Success: Finished initializing build environment
    13:15:27.988 Cloning repository...
    13:15:32.126 Success: Finished cloning repository files
    13:15:33.008 Installing dependencies
    13:15:33.011 Python version set to 2.7
    13:15:34.226 v12.18.0 is already installed.
    13:15:34.736 Now using node v12.18.0 (npm v6.14.4)
    13:15:34.777 Started restoring cached build plugins
    13:15:34.781 Finished restoring cached build plugins
    13:15:34.910 Attempting ruby version 2.7.1, read from environment
    13:15:35.944 Using ruby version 2.7.1
    13:15:36.221 Using PHP version 5.6
    13:15:36.252 5.2 is already installed.
    13:15:36.257 Using Swift version 5.2
    13:15:36.258 Started restoring cached node modules
    13:15:36.260 Finished restoring cached node modules
    13:15:36.262 Started restoring cached yarn cache
    13:15:36.265 Finished restoring cached yarn cache
    13:15:36.268 Installing yarn at version 1.22.4
    13:15:36.272 Installing Yarn!
    13:15:36.272 > Downloading tarball...
    13:15:36.281
    13:15:36.281 [1/2]: https://yarnpkg.com/downloads/1.22.4/yarn-v1.22.4.tar.gz --> /tmp/yarn.tar.gz.zT8fSfiMql
    13:15:36.282   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
    13:15:36.283                                  Dload  Upload   Total   Spent    Left  Speed
    13:15:36.369
      0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
    100    79  100    79    0     0    900      0 --:--:-- --:--:-- --:--:--   908
    13:15:36.506
    100    93  100    93    0     0    413      0 --:--:-- --:--:-- --:--:--   413
    13:15:36.616
      0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
    100   625  100   625    0     0   1868      0 --:--:-- --:--:-- --:--:-- 11574
    13:15:36.722
    100 1215k  100 1215k    0     0  2758k      0 --:--:-- --:--:-- --:--:-- 2758k
    13:15:36.722
    13:15:36.722 [2/2]: https://yarnpkg.com/downloads/1.22.4/yarn-v1.22.4.tar.gz.asc --> /tmp/yarn.tar.gz.zT8fSfiMql.asc
    13:15:36.735
    100    83  100    83    0     0   6655      0 --:--:-- --:--:-- --:--:--  6655
    13:15:36.743
    100    97  100    97    0     0   4617      0 --:--:-- --:--:-- --:--:--  4617
    13:15:36.746
    100   629  100   629    0     0  26002      0 --:--:-- --:--:-- --:--:-- 26002
    13:15:36.749
    100  1028  100  1028    0     0  38460      0 --:--:-- --:--:-- --:--:-- 38460
    13:15:36.780 > Verifying integrity...
    13:15:36.805 gpg: Signature made Mon 09 Mar 2020 03:52:13 PM UTC using RSA key ID 69475BAA
    13:15:36.812 gpg: Good signature from "Yarn Packaging <[email protected]>"
    13:15:36.815 gpg: Note: This key has expired!
    13:15:36.815 Primary key fingerprint: 72EC F46A 56B4 AD39 C907  BBB7 1646 B01B 86E5 0310
    13:15:36.815      Subkey fingerprint: 6D98 490C 6F1A CDDD 448E  4595 4F77 6793 6947 5BAA
    13:15:36.816 > GPG signature looks good
    13:15:36.816 > Extracting to ~/.yarn...
    13:15:36.868 > Adding to $PATH...
    13:15:36.872 > We've added the following to your /opt/buildhome/.bashrc
    13:15:36.873 > If this isn't the profile of your current shell then please add the following to your correct profile:
    13:15:36.873
    13:15:36.873 export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH"
    13:15:36.873 
    13:15:37.182 > Successfully installed Yarn 1.22.4! Please open another terminal where the `yarn` command will now be available.
    13:15:37.503 Installing NPM modules using Yarn version 1.22.4
    13:15:37.900 yarn install v1.22.4
    13:15:37.944 [1/4] Resolving packages...
    13:15:37.969 [2/4] Fetching packages...
    13:15:38.493 [3/4] Linking dependencies...
    13:15:38.547 [4/4] Building fresh packages...
    13:15:38.555 Done in 0.66s.
    13:15:38.572 NPM modules installed using Yarn
    13:15:38.787 Installing Hugo 0.88.1
    13:15:39.639 hugo v0.88.1-5BC54738+extended linux/amd64 BuildDate=2021-09-04T09:39:19Z VendorInfo=gohugoio
    13:15:39.640 Started restoring cached go cache
    13:15:39.643 Finished restoring cached go cache
    13:15:39.644 Installing Go version 1.17
    13:15:45.024
    13:15:45.024 unset GOOS;
    13:15:45.024 unset GOARCH;
    13:15:45.024 export GOROOT='/opt/buildhome/.gimme_cache/versions/go1.17.linux.amd64';
    13:15:45.024 export PATH="/opt/buildhome/.gimme_cache/versions/go1.17.linux.amd64/bin:${PATH}";
    13:15:45.024 go version >&2;
    13:15:45.024
    13:15:45.026 export GIMME_ENV="/opt/buildhome/.gimme_cache/env/go1.17.linux.amd64.env"
    13:15:45.033 go version go1.17 linux/amd64
    13:15:45.034 Installing missing commands
    13:15:45.034 Verify run directory
    13:15:45.035 Executing user command: hugo
    13:15:45.101 Start building sites …
    13:15:45.101 hugo v0.88.1-5BC54738+extended linux/amd64 BuildDate=2021-09-04T09:39:19Z VendorInfo=gohugoio
    13:15:45.658
    13:15:45.659                    | EN
    13:15:45.659 -------------------+-----
    13:15:45.659   Pages            | 73
    13:15:45.659   Paginator pages  |  1
    13:15:45.659   Non-page files   | 24
    13:15:45.659   Static files     | 96
    13:15:45.659   Processed images |  0
    13:15:45.659   Aliases          | 21
    13:15:45.659   Sitemaps         |  1
    13:15:45.659   Cleaned          |  0
    13:15:45.659
    13:15:45.659 Total in 601 ms
    13:15:45.665 Finished
    13:15:45.665 Validating asset output directory
    13:15:46.875 Deploying your site to Cloudflare's global network...
    13:15:53.603 Success: Your site was deployed!
    

    Yay! 🎉

  5. Now, marvel at the deployed site!

    • The deployed site has a TLS certificate provided by Cloudflare 😎.
    • Deployments can be rolled back (to any version) at any time.
    • Each deployment has its own unique subdomain, such as https://331e1fb6.cloudflarepagespoc.pages.dev/ for example, should that be of use.
    • Access to Preview deployments can be controlled with Cloudflare Access.
    • Web Analytics can be enabled at Cloudflare.
    • Check your site’s score on Google’s PageSpeed Insights… for example, here’s this site

Preview Site

Preview branches are classified as “all non-Production branches” i.e. any branch that isn’t main. Try one out…

  1. Create a branch… git checkout -b my-preview-version-1
  2. Make some changes
  3. Commit your changes (repeat the previous step for more changes, or continue)
  4. Push to the repo… git push origin
  5. Watch Cloudflare Pages pick up the branch/changes and deploy a preview! The deployment again gets its own subdomain, such as https://df58e22a.cloudflarepagespoc.pages.dev/ for example, however it also gets a handy alias containing the branch name, such as https://my-preview-version-1.cloudflarepagespoc.pages.dev

When you’re ready to merge the branch to main, open a pull request, watch as the Cloudflare Pages build completes to allow your Merge. Once merged a Production build and deployment will be trigger by default.

Adding a (custom, dynamically generated) Cloudflare Worker

To show the build process can include more than just the static site generation, we’ll add a Cloudflare Worker to act as a simple API. The worker will be dynamically generated as well, just “because we can”.

The api will simply return a json response containing the worker’s code creation time, which will be updated each time there’s a deployment, as well as the current time whenever the api is called. The result will therefore be something like:

1
2
3
4
{
  "time_build": "2021-09-15T17:02:03Z",
  "time_now": "The local time in GB is 6:24:40 PM"
}
  1. Add environment variables CF_ shown below:

    Variable nameValue
    CF_WORKERS_API_TOKEN<ADD API TOKEN HERE>
    CF_ACCOUNT_ID<ADD ACCOUNT ID HERE>
    CF_ZONE_ID<ADD ZONE ID HERE>
    HUGO_VERSION0.88.1
    HUGO_ENVIRONMENTproduction
  2. Add the cloudflare worker deployment code as seen in the repo.

  3. Add code to hook in to the Hugo deployment as seen in the repo’s Makefile. Of course, there are other methods that could be employed to do this kind of thing too, such as GitHub Workflows, but this way allows the scope of this PoC to be contained. The Makefile allows other helper commands too, btw.

  4. Update the Cloudflare Pages Build command from the regular hugo command to now use the Makefile with the command make cloudflare-deploy

  5. Add a CNAME to your DNS to allow the api to be reached e.g. CNAME api-poc shadowcryptic.com

Deployment should now include/update this Cloudflare worker whenever the site is updated/deployed (the time_build field should be seen to update).

Clicking on the api endpoint should provide something like the above example. We could of course add a simple piece of JavaScript to a page that automatically calls the API upon load and updates a page dynamically, but that’s another story.

Adding dynamic elements to the Hugo generated SSG is now fairly simple to develop and deploy using Cloudflare Pages.

🥂 😎 🥂