Deploying al-folio to Cloudflare Pages with GitHub Actions

In 2024, I wrote about adapting al-folio’s deployment workflow for Cloudflare Pages. That approach built the site with GitHub Actions, but it chained deployment to the link-checking workflow and published with cloudflare/pages-action@v1. The action is no longer available, and al-folio’s workflows have changed substantially since then.

The underlying idea remains useful: build al-folio with the toolchain its maintainers test, then send the generated static site to Cloudflare. This updated approach keeps the build and deployment in one GitHub Actions workflow and publishes _site with Wrangler.

Starting Point

This guide assumes your repository was created from the al-folio template and still has its .github/workflows/deploy.yml workflow. It describes the generic template rather than the customized workflow used by this website, so compare the examples with the current upstream file before applying them.

The resulting pipeline is straightforward:

  1. A push or pull request on a configured branch starts deploy.yml.
  2. GitHub Actions checks out that revision and builds the site.
  3. The Cloudflare step runs only if the build succeeds.
  4. A push to the production branch updates production. Other configured branches and same-repository pull requests create previews.

Why Keep the Build in GitHub Actions?

Cloudflare Pages has its own build system, but using it would mean maintaining a second definition of the al-folio build. The current upstream workflow already prepares Ruby and Bundler, Python and nbconvert, Node and npm, ImageMagick, Jekyll, and PurgeCSS. Reusing those steps keeps the deployment aligned with the template as its requirements evolve.

Cloudflare therefore receives the already-built _site directory instead of rebuilding the project.

Configure Cloudflare Pages

Cloudflare recommends a Direct Upload project when another platform owns the build. Create the project with Wrangler and select your repository’s production branch, usually main or master, when prompted.

An existing project created with Cloudflare’s Git integration can also accept Wrangler deployments. Disable automatic production and preview deployments under Workers & Pages > your project > Settings > Builds & deployments so that a commit does not start both a Cloudflare build and a GitHub Actions build.

Create an API token with Account > Cloudflare Pages > Edit permission. Add these secrets under Settings > Secrets and variables > Actions in your GitHub repository:

  • CLOUDFLARE_API_TOKEN
  • CLOUDFLARE_ACCOUNT_ID

Cloudflare’s continuous-integration guide documents both values and the required permissions.

Finally, configure _config.yml for the Cloudflare URL. A site served from the root of a Pages domain normally has an empty baseurl; the al-folio template’s /al-folio value is intended for its GitHub Pages demo.

Choose Which Branches Build

Define the push and pull-request triggers directly in deploy.yml. For example, this configuration builds every commit pushed to main or preview, as well as pull requests targeting either branch:

on:
  push:
    branches:
      - main
      - preview
  pull_request:
    branches:
      - main
      - preview
  workflow_dispatch:

The pull_request.branches list matches the pull request’s target branch, not its source branch. Use "**" in either list to match every branch.

The upstream workflow also uses paths filters. GitHub requires both the branch and path filters to match, so remove paths if literally every commit on the selected branches should build. Retain the filters if documentation-only or other excluded changes should not deploy.

There is no need to start deploy.yml through broken-links.yml or a workflow_run event. Direct triggers preserve the push or pull-request context and ensure that the revision being built is the revision sent to Cloudflare.

Preserve the Upstream Build

Keep the Ruby, Python, Node, dependency-installation, Jekyll, and PurgeCSS steps from the current upstream workflow. These produce the _site directory that Wrangler uploads.

Change the workflow permissions so Wrangler can create a GitHub Deployment:

permissions:
  contents: read
  deployments: write

Then replace the final GitHub Pages publishing step with the following step. It must appear after the build and post-processing steps.

- name: Deploy to Cloudflare Pages 🚀
  id: cloudflare
  if: >-
    success() &&
    (github.event_name != 'pull_request' ||
    github.event.pull_request.head.repo.full_name == github.repository)
  uses: cloudflare/wrangler-action@v4
  with:
    apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
    accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
    command: >-
      pages deploy _site
      --project-name=YOUR_PROJECT_NAME
      --branch=${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}
    gitHubToken: ${{ secrets.GITHUB_TOKEN }}

Replace YOUR_PROJECT_NAME with the Cloudflare Pages project name. The --branch expression assigns a stable pr-N alias to pull-request previews and uses the Git branch name for push and manual runs. Cloudflare treats the branch configured as the project’s production branch as production; other branch names become previews.

Deployment Behavior

Event Build Cloudflare deployment
Push to the production branch Yes Production
Push to another configured branch Yes Branch preview
Pull request from the same repository Yes pr-N preview
Pull request from a fork Yes Skipped
Manual run on the production branch Yes Production
Manual run on another branch Yes Branch preview

The repository comparison in the deploy condition protects pull requests from forks. GitHub does not provide repository secrets to those runs, so they can validate the build but cannot authenticate to Cloudflare. Pull requests opened from branches in the same repository can use the secrets and receive a preview deployment.

What About the Other al-folio Workflows?

Only workflow files committed under .github/workflows in your repository run for your project. Workflows running in the upstream al-folio repository do not propagate to repositories created from the template.

Formatting, unit tests, link checking, accessibility checks, and deployment each have their own triggers. They do not automatically call or wait for one another. If another check must block Cloudflare deployment, add it before the deploy step or make deployment a separate job that lists the required jobs under needs.

When copying an upstream workflow, also inspect repository-specific conditions. A job guarded by github.repository == 'alshedivat/al-folio', for example, intentionally skips in a template-derived repository until the condition is removed or adapted.

Conclusion

This configuration keeps al-folio’s evolving build requirements in GitHub Actions while using Cloudflare Pages as the static host. Every matching push or pull request builds first, failed builds never reach the deployment step, the production branch updates the public site, and other configured branches receive preview URLs.




Enjoy Reading This Article?

Here are some more articles you might like to read next: