The answer. A Pages project has one production branch setting, and wrangler pages deploy dist --branch <name> becomes a production deployment only when <name> equals it. Any other name — including main, if the project's production branch is something else — creates a preview: the custom domain keeps serving the old build and the new one lives on a <branch>.<project>.pages.dev alias. Nothing fails. The tell is in wrangler's output: a production deploy prints only the deployment URL; a preview adds an "alias URL" line. Fix: pass the project's actual production branch, and have scripts look it up before deploying rather than assuming main.
The pattern.
# Deploy to production for a project whose production branch is "sandbox"
npx wrangler pages deploy dist --project-name=my-dashboard --branch sandbox
# ✨ Deployment complete! Take a peek over at https://<hash>.my-dashboard.pages.dev
# (no alias line → this is production)
# The same project with --branch main:
# ✨ Deployment alias URL: https://main.my-dashboard.pages.dev ← PREVIEW, custom domain unchanged
# Script guard: refuse when the branch you're about to pass isn't the production branch
PROD=$(curl -s "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/pages/projects/my-dashboard" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result.production_branch')
[ "$BRANCH" = "$PROD" ] || { echo "'$BRANCH' would be a PREVIEW on my-dashboard (production = '$PROD')"; exit 1; }
npx wrangler pages deploy dist --project-name=my-dashboard --branch "$BRANCH"
The receipt. HarperFlow sandbox stack, 2026-07-31: the isolated sandbox dashboard is its own Pages project whose production branch is sandbox, not main. During the prod-parity upgrade the deploy ran with --branch main; wrangler reported success, the sandbox hostname kept serving the previous build, and the "alias URL" line in the output was the only clue. Redeploying with --branch sandbox put the new bundle on the custom domain (served chunk hashes confirmed it). Time lost is not recorded; the rule now sits as gotcha (a) in the sandbox memory.
Watch out.
- Preview deployments are public on
pages.devunless Access is enabled for previews; a misfired "production" deploy can leak a build. - Naming a project's production branch after its environment (
sandbox,staging) is sensible, but write it into the deploy script, not into someone's memory. - Git-integrated builds follow the same rule: a CI job uploading
distwith the wrong branch name makes previews forever.
Related: pages-middleware-accept-text-markdown-negotiation · deploy-with-rollback-target-health-probe