Pages Deployment
Deploying static sites with wrangler pages deploy
Creating a Pages Project
Before your first deploy, create the project:
npx wrangler@4 pages project create zudo-cloudflare --production-branch=mainPass --production-branch explicitly — without it, project create needs a production branch and would normally prompt for one interactively. In CI there's no TTY to prompt against, so it doesn't hang: it fails immediately with Must specify a production branch.
Warning
Don't rely on pages deploy to create the project for you in CI. Cloudflare's own docs describe an auto-create path for pages deploy, but it only fires through an interactive prompt for the project name and production branch — a GitHub Actions runner has no TTY, so that prompt never appears. Worse, once you pass --project-name (as the deploy command below does, which you need in CI for an auditable log), wrangler requires the project to already exist and fails with Project not found. The specified project name does not match any of your existing projects. [code: 8000007] instead of creating one. Always run pages project create before the first CI deploy. This applies to current wrangler@4 — the auto-create prompt is documented as interactive-only, with no non-interactive fallback.
For CI pipelines, make the create step idempotent so re-runs don't fail once the project already exists:
set +e
create_output=$(npx wrangler@4 pages project create zudo-cloudflare \
--production-branch=main 2>&1)
create_exit=$?
set -e
if [ "$create_exit" -ne 0 ]; then
if echo "$create_output" | grep -qiE "already exists|8000077|duplicate"; then
echo "Project already exists, continuing."
else
echo "$create_output" >&2
exit "$create_exit"
fi
fiOnly tolerate the "already exists" family of signatures (already exists, the 8000077 API error code, duplicate). Any other failure — an auth error, a rate limit, a typo'd account ID — should still fail the pipeline instead of being silently swallowed.
Deploy Command
The core deploy command:
npx wrangler@4 pages deploy <directory> \
--project-name=<project-name> \
--branch=<branch> \
--commit-hash="${GITHUB_SHA}" \
--commit-message="Production deploy: ${GITHUB_SHA}"Parameters
| Parameter | Description |
|---|---|
<directory> | The directory containing built static files |
--project-name | The Pages project name (must match what was created) |
--branch | Branch name. main triggers production; anything else is a preview |
--commit-hash | Git SHA for tracking (optional but recommended) |
--commit-message | Deploy description (optional) |
Environment Variables
The deploy command needs these environment variables:
CLOUDFLARE_API_TOKEN=<your-token>
CLOUDFLARE_ACCOUNT_ID=<your-account-id>Production vs Preview
Production: Deploy with
--branch=main. This updates the primary URL.Preview: Deploy with any other branch name (e.g.,
--branch=pr-42). This creates a preview URL.
Warning
The --branch flag determines whether the deploy is production or preview. Always use main for production deploys, not your actual branch name.
Deploy Directory Structure
The deploy directory must contain the final files as they should be served. If your site uses a base path (e.g., /), you need to nest files accordingly:
mkdir -p deploy/pj/my-site
cp -r dist/* deploy/pj/my-site/
echo '/ /pj/my-site/ 302' > deploy/_redirects
npx wrangler@4 pages deploy deploy --project-name=my-site --branch=mainSee Base Path Pattern for details.
Pages CI Troubleshooting
Common failure signatures when running pages project create / pages deploy in CI, and what actually causes them:
| Signature | Cause | Fix |
|---|---|---|
[code: 10000] / Authentication error | CLOUDFLARE_API_TOKEN is invalid, expired, or missing the Pages permission | Regenerate a token with the "Cloudflare Pages — Edit" account permission and update the CI secret |
More than one account available but unable to select one in non-interactive mode | CLOUDFLARE_ACCOUNT_ID is unset and the token has access to multiple accounts, so wrangler can't prompt for one | Set CLOUDFLARE_ACCOUNT_ID explicitly (see Environment Variables above) |
| Idempotent create step still fails even though the project exists | The captured output didn't match already exists, 8000077, or duplicate | Print create_output — the pattern only tolerates that signature family on purpose; anything else (auth, rate limit, quota) is a real failure |
Install step fails before wrangler even runs (e.g. ERR_PNPM_OUTDATED_LOCKFILE) | package.json drifted from the committed lockfile and the CI install runs with a frozen lockfile | Run the install locally without --frozen-lockfile and commit the updated lockfile |
| Deploy fails repeatedly with 5xx / gateway errors after retries | Cloudflare API instability, not a config problem | Check Cloudflare's status page before re-running the job |