Branch Deploy Controls on Cloudflare Pages

Cloudflare Pages deploys every branch you push by default. For a small site with a handful of contributors that is convenient: every branch gets a preview URL without anyone configuring anything. On a busier repository it becomes noise and cost. Renovate opens a branch per dependency update, each of which builds; writers push work-in-progress branches several times an hour; branches that only touch the README or a workflow file still trigger a full site build; and dozens of preview URLs accumulate, each publicly reachable at a guessable hostname.

This guide configures branch deploy controls for a documentation site on Pages with about 30 contributors: which branches deploy at all, how preview aliases are named and shared, how builds are skipped for changes that cannot affect the site, and how previews are kept private. It is part of Cloudflare Pages Edge Caching Setup, and the general practice of previews is covered in Preview Environments for Pull Requests.

Prerequisites

  • A Pages project connected to GitHub or GitLab (Git integration), or deployed with wrangler pages deploy from your own CI.
  • A naming convention for branches — here docs/* for content work, feat/* and fix/* for code, renovate/* for dependency updates.
  • Cloudflare Zero Trust (Access) available on the account, for private previews.

The Starting Point

A month of build history before any controls:

Source of buildsBuilds per monthUseful previews
main (production)118all
docs/* branches640most, but many were intermediate pushes
feat/*, fix/*210all
renovate/*290few — reviewed through CI checks, not previews
README, workflow, config-only changes170none
Total1,428

The build queue on the project's plan allowed one concurrent build; at peak times writers waited over 20 minutes for their preview because Renovate branches were ahead of them in the queue.

Monthly Cloudflare Pages builds by branch type before controls Horizontal bars of builds per month. Docs branches 640. Renovate branches 290. Feature and fix branches 210. Config-only changes that could not affect the site 170. Main 118. Total 1,428. Builds per month by source, before controls docs/* branches 640 renovate/* branches 290 feat/*, fix/* 210 Config-only changes 170 main (production) 118
From the Pages project's deployment history over 30 days.

Step 1: Choose Which Branches Deploy

In the project's Settings → Builds & deployments, set Preview branch deployments to "Custom branches", then include and exclude patterns:

Production branch:        main
Preview branches include: docs/*, feat/*, fix/*
Preview branches exclude: renovate/*, dependabot/*, wip/*

Renovate branches no longer build on Pages; their changes are validated by the GitHub Actions build and tests, which is where dependency problems show up anyway. A wip/* prefix gives writers a way to push work without triggering previews at all. The same settings are available through the API and Terraform (cloudflare_pages_project with source.config.preview_branch_includes and preview_branch_excludes), which keeps them reviewable.

Step 2: Skip Builds for Changes That Cannot Affect the Site

Build watch paths limit builds to commits that touch the site's inputs:

Include paths: src/*, content/*, public/*, astro.config.mjs, package.json, package-lock.json
Exclude paths: *.md at the repository root, .github/*

A commit that only changes README.md, CONTRIBUTING.md or a workflow file now shows as "skipped" in the Pages dashboard. For one-off cases, [CI Skip] in a commit message skips the Pages build too.

Watch paths need care: a change to a shared data file outside the listed paths, or a new top-level config file, would silently not trigger a build. The team added a monthly review of the paths list and a note in the contributing guide.

Branch and path checks before a Pages preview build A push arrives. If the branch is main, it builds production. If the branch matches an excluded pattern such as renovate or wip, nothing builds on Pages. If the branch matches an included pattern, the changed paths are checked, and a build starts only if a watched path changed. Otherwise the deployment is marked as skipped. Does this push build a preview? Push any branch Branch rules include docs, feat, fix Watch paths src, content, public, config Build preview alias per branch
Two cheap checks removed 45% of builds without removing a single useful preview.

Step 3: Stable, Shareable Preview Aliases

Each preview deployment has a unique hash URL (3f9c1a2b.docs-site.pages.dev) and a branch alias (docs-search-tuning.docs-site.pages.dev for docs/search-tuning). The alias always points at the latest deployment of the branch, which is what reviewers should get: a link that stays valid as the author pushes fixes. Branch names are normalized — lowercase, non-alphanumeric characters become hyphens, and long names are truncated — so a GitHub Action that comments the preview link on the pull request computes the alias from the branch name rather than guessing:

const alias = branch.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 28);
const url = `https://${alias}.docs-site.pages.dev/`;

The comment workflow, and how to update rather than duplicate comments, is described in Commenting on Preview Deploys.

Step 4: Keep Previews Private

Preview hostnames follow a predictable pattern, and work-in-progress documentation can include unreleased features. Two layers keep previews private:

  • Cloudflare Access for previews. In the Pages project settings, enabling access policy for preview deployments puts *.docs-site.pages.dev (excluding the production alias) behind an Access application. Contributors sign in with the company identity provider; reviewers outside the company get one-time PIN access by email when needed.
  • noindex on non-production. Pages adds X-Robots-Tag: noindex to preview deployments by default; a _headers rule makes it explicit, and the production custom domain is excluded, as in robots.txt and noindex for Preview and Staging.

The production deployment's own pages.dev hostname is not a preview and is not covered by the preview Access policy; a Bulk Redirect rule sends it to the custom domain.

Access rules for production, preview and pages.dev hostnames A matrix of hostnames. The production custom domain is public and indexable. Preview branch aliases and hash URLs require sign-in through Cloudflare Access and carry noindex. The production pages.dev hostname redirects to the custom domain with a 301. Who can reach which hostname Hostname Access Indexing docs.example.com public indexable branch.docs-site.pages. dev Access sign-in noindex hash.docs-site.pages.dev Access sign-in noindex docs-site.pages.dev 301 to custom domain not served
Access for previews is one project setting; the redirect is one Bulk Redirect rule.

Direct Uploads From Your Own CI

Teams that build in GitHub Actions and upload with wrangler pages deploy dist --branch "$BRANCH" get the same aliases and Access protection, but branch controls in the Pages dashboard do not apply to direct uploads — the workflow decides. The equivalent rules then live in the workflow's on: filters and paths settings, and a job-level if: skips Renovate branches. This site used the Git integration for simplicity; a sibling site with a heavier build used direct uploads, following Automating Preview Deploy Pipelines with GitHub Actions.

Cleaning Up and Explaining the Rules

Pages keeps every deployment, including previews for branches deleted long ago, and each one remains reachable at its hash URL until removed. With Access on previews that is no longer a leak, but the list becomes noise in the dashboard and in any tooling that enumerates deployments. A weekly scheduled job lists preview deployments through the Pages API and deletes those whose branch no longer exists or whose last deployment is older than 30 days, following Cleaning Up Stale Preview Deployments. The first run removed 1,140 old previews.

The rules are only useful if contributors know them. The contributing guide gained a short table — branch prefix, whether it builds a preview, how to share it — and the pull request template links to it. Most confusion in the first week came from a writer naming a branch docs-fix-typo instead of docs/fix-typo, which matched no include pattern; after the table was added, the question never came up again.

Measured Impact

MeasureBeforeAfter
Builds per month1,428786
Median wait for a preview at peak times21 min3 min
Useful previews lost—0 (none reported in 3 months)
Publicly reachable preview URLs160+0
Preview URLs indexed by search engines120

Pitfalls & Rollback

  • Watch paths that miss an input. A build silently does not happen; review the paths list when adding config files.
  • Excluding branches that need previews. Communicate the naming convention and the wip/* escape hatch.
  • Assuming dashboard controls apply to direct uploads. They do not; put the rules in the workflow.
  • Access without a reviewer path. External reviewers need one-time PIN or a guest policy.
  • Rollback: every control is a project setting; switching preview deployments back to "All branches" restores the default immediately.

Conclusion

Default "deploy every branch" is fine until it is not. Include only the branch patterns that need previews, exclude dependency bots and work-in-progress branches, skip builds for changes that cannot affect the site, share stable branch aliases, and put previews behind Access. On this repository that halved the build count, cut preview waits at busy times from twenty minutes to three, and took every preview off the public internet.

FAQ

What are branch deploy controls on Cloudflare Pages?

Project settings that decide which Git branches trigger deployments. You can deploy all non-production branches, none, or a set chosen with include and exclude patterns, and separately choose the production branch.

How do preview aliases work?

Each preview deployment gets a unique URL with a hash, plus a branch alias such as feature-search.project.pages.dev that always points at the latest deployment of that branch. Branch names are normalized, so slashes and uppercase letters become hyphens and lowercase.

Can I skip a build for commits that do not change the site?

Yes. Cloudflare Pages skips builds when the commit message contains CI Skip or similar markers, and a build watch paths setting can limit builds to changes in specific directories. With direct uploads from your own CI, you decide when to deploy.

How do I keep previews private?

Put Cloudflare Access in front of the preview hostnames, which requires sign-in with your identity provider, and send X-Robots-Tag noindex on non-production deployments. Pages can enable Access for preview deployments with a single project setting.

Filed underHow-tosCloudflare