Cloudflare pages direct upload with stable preview urls

Cloudflare pages direct upload with stable preview urls

I switched all my projects to Monorepos this year, and I use Cloudlfare Pages intensively for hosting static websites. There is one small problem with Cloudlfare’s Github integration: you can only connect one project per repository. In a monorepo where I provide pages like a landing page, documentation and an app, this is a problem.

It’s good that you can also upload the assets directly. The problem with that: you lose some nice benefits:

And those are already quite nice benefits ;) So I started to rebuild the benefits myself. With the help of Wrangler and the Cloudflare API it is not difficult to achieve everything.

To get a stable URL I originally assumed that I would just get an updated stable URL with the help of the branch name.

npm i -g wrangler
cd ${{ env.ROOT_DIRECTORY }}
CF_PUBLISH_OUTPUT=$(wrangler pages deploy ${{ env.DIST_DIRECTORY }} --project-name=${{ env.CLOUDFLARE_PAGES_PROJECT_NAME }} --branch="${{ steps.extract_branch.outputs.branch }}" --commit-dirty=true --commit-hash=${{ steps.meta.outputs.sha_short }} | grep complete)
echo "cf_deployments=$CF_PUBLISH_OUTPUT" >> "$GITHUB_OUTPUT"

Unfortunately, after a few test runs, I found that this is not the case. I didn’t deal with it further at this point, but tried to take an alternative approach:

For reading and deleting deployments I wrote a small TypesScript program that I run in the CI pipeline.

Read out all previous branch deployments:

public async getDeployments(options?: { branch?: string }) {
    const { branch } = options || {}
    const { accountId, projectName, apiToken } = this.config

    const response = await fetch(
      `https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}/deployments`,
      {
        headers: {
          Authorization: `Bearer ${apiToken}`,
        },
      },
    ).then((res) => res.json())

    let deployments = response.result

    if (branch) {
      deployments = deployments.filter(
        (deployment) =>
          deployment.deployment_trigger?.metadata?.branch === branch,
      )
    }

    return deployments
}

Deleting a deployment:

public async deleteDeployment(id: string) {
    const { accountId, projectName, apiToken } = this.config
    await fetch(
      `https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${projectName}/deployments/${id}?force=true`,
      {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${apiToken}`,
        },
      },
    ).then((res) => res.json())
}

The approach has another advantage: deployments that are no longer current are always cleaned up, since I am no longer interested in them anyway.

typescript nodejs github actions cloudflare
Published on 2023-09-30, last updated on 2023-12-02 by Adam
Comments or questions? Open a new discussion on github.
Adam Urban

Adam Urban is fullstack engineer, loves serverless and generative art, and is building side projects in his free time. Latest projects are flethy.com, ethme.at and diypunks.xyz.