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.
Adam Urban is fullstack engineer, loves serverless and generative art, and is building side projects like weeklyfoo.com, flethy.com and diypunks.xyz in his free time.