Deployment

Direct Upload vs Git Integration on Cloudflare Pages

Cloudflare’s documentation presents Direct Upload and Git integration as two ways to do the same thing. They are not. They differ in who runs the build, and that one difference decides your build environment, your deploy trigger, and what you are looking at when a deployment goes wrong.

This is the comparison I wanted when I set up the site you are reading.

Who runs the build

With Git integration, you hand Cloudflare two things: a repository and a build command. Cloudflare clones the repo, installs dependencies, runs the build on its own machine, and publishes whatever appears in the output directory. You never see that machine, and you cannot inspect it when it disagrees with your laptop.

With Direct Upload, you build locally — or on your own CI — and wrangler pushes the finished directory to Pages. No npm install runs on Cloudflare’s side. No build command runs. Cloudflare receives files.

Everything below follows from that one inversion.

The full Direct Upload path

Two commands create the project and ship it:

npx wrangler pages project create your-project --production-branch=main

npm run build
npx wrangler pages deploy dist --project-name=your-project --branch=main

For scale, on a ten-page Astro site with two content collections plus RSS and sitemap endpoints:

StepMeasured
astro build — 10 pages8.1s
Upload to Pages — 17 files2.4s
Live on the edgea few seconds

Seventeen files is the entire payload of a text-first content site before images: the HTML pages, one stylesheet, robots.txt, ads.txt, llms.txt, and the XML endpoints. There is not much to upload, and the timings show it.

Deployments are also incremental. Wrangler compares content and uploads only what changed. Three consecutive deploys of this site moved 17 files, then 5, then 1 — the last of those after editing a single paragraph, which changed that article’s page and nothing else. The upload step stays cheap as the archive grows.

Where the two paths actually differ

Git integrationDirect Upload
Build machineCloudflare’s build imageYours
Deploy triggergit pushYou run wrangler
Server-side buildYesNone
Node and dependency versionsWhatever the build image carriesExactly what you have
Preview URLsOne per pull request, automaticOnly with a non-production --branch
RollbackDashboard, one clickDashboard, one click
Build secretsStored in the dashboardStay in your local environment
Project works without a repoNoYes

The version row is the one people underestimate. “It builds on my machine” is not a promise that it builds on Cloudflare’s, and the failure mode there is an email about a broken deployment rather than a red line in your terminal.

Direct Upload removes that entire class of problem: you ship the exact bytes you tested. The price is that nothing deploys unless you run the command. There is no push-and-forget.

The API makes the difference visible. Ask for a Direct Upload project and there is no source block in the response at all — nothing describes a repository, because there is not one:

curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT" \
  -H "Authorization: Bearer $API_TOKEN"

The flag that catches people out

--branch does not choose an environment. It labels the deployment with a branch name, and Cloudflare maps that name to an environment. Wrangler’s own help text says it plainly: the name of the branch you want to deploy to.

  • --branch=main, where main is your production branch → production
  • --branch=staging → preview, served at staging.your-project.pages.dev
  • omitted entirely → production

So it is not a production switch. It is an identity tag. Get the name wrong and you will watch a clean, successful upload land on a preview URL while the live site keeps serving the previous build — with a green success message in your terminal the whole way through.

The setting the dashboard will not let you change

Create a project through Direct Upload and the production branch field becomes read-only. Run npx wrangler pages project create without --production-branch and you can end up locked to a value you did not intend.

Cloudflare’s documentation is explicit that the fix is an API call:

curl --request PATCH \
  "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT" \
  --header "Authorization: Bearer $API_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"production_branch": "main"}'

Set the branch name at creation time. Changing it afterwards is a different kind of task than the dashboard implies.

Limits worth knowing before you hit them

Upload methodFile countPer-file size
Wrangler20,00025 MiB
Drag and drop1,00025 MiB

A text-first site will never approach either number. A site with a large image library can. If you are near the ceiling, move images to a separate asset host rather than reshaping your build to dodge a limit.

One more: drag-and-drop deployments cannot compile a functions/ directory. If you need Pages Functions, the upload has to go through Wrangler.

Which to pick

Choose Git integration when:

  • Several people deploy, and you want shipping to be a side effect of merging
  • You want preview URLs on every pull request without thinking about it
  • The build is slow and you would rather not run it on your laptop

Choose Direct Upload when:

  • You are on a machine that already holds the build output, and a round trip through a repository adds nothing
  • You want the deployed artifact to be byte-identical to the one you tested
  • Your build needs credentials you would rather not paste into a dashboard
  • What you are deploying is not in a repository at all

The hybrid is common and worth considering: keep the repository, and let GitHub Actions run wrangler pages deploy as the final step. You keep version history and a CI record, but the build environment becomes one you defined in a workflow file rather than one you inherited.

Where Pages stops being the right answer

Pages is a static host with an optional edge runtime. It is the wrong tool when you need a database on the same origin, server-side sessions that outlive a single request, or a scheduled job.

You can add those with Workers and D1, and for plenty of projects that is the right call. But at that point you are operating an application, and the hosting decision deserves its own evaluation rather than being inherited from “there is already a Pages project here”.

For a content site, static hosting remains the cheapest and fastest option available. That is the case Direct Upload was built for, and for a one-person publication it is usually the shorter path.


Written by TestedHost. Every recommendation on this site comes from running the setup described, on a live deployment — not from a vendor spec sheet. Spotted something out of date? Tell us.