← Back Cloudflare pages direct upload with stable preview urls u11g.com
9/30/2023

Cloudflare pages direct upload with stable preview urls

#cloudflare#typescript#nodejs#github actions

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:

  • Stable preview URL
  • PR comment with the links 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:

  • Search all deployments to a branch on every run.
  • Delete all deployments
  • Upload new assets 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.