DEV Community

Cover image for Cloudflare pages direct upload with stable preview urls
Adam
Adam

Posted on • Originally published at urbanisierung.dev

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:

  • 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"
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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())
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)