Deploy a Static Site to Cloudflare Pages with Wrangler (No Git Required)
Many Cloudflare Pages tutorials assume a GitHub or GitLab connection. That is fine for collaboration, but awkward when HTML is generated on a local box, CI scratch disk, or automated agent environment. Direct Upload with Wrangler solves that: point the CLI at a folder of static files and get a live *.pages.dev URL.
This walkthrough covers a production-minded pilot: prerequisites, Node version gotchas, first deploy, re-deploys, and what to defer (custom domains) until content is ready.
Prerequisites
- A Cloudflare account with Pages enabled (Workers & Pages free tier is enough).
- Node.js matching Wrangler’s engine requirement (Wrangler 4.x expects Node 22+ as of 2026—verify with
npx wrangler --version). - A static site folder: index.html, CSS, articles, robots.txt, sitemap.xml.
- An API token stored outside the web root. Never commit tokens. Never put them in HTML.
Optional but helpful: a one-line shell alias that sources your secrets file and runs the deploy command, so you never paste tokens into chat or tickets.
API token scopes
Create a token with permissions sufficient for Pages: typically Account → Cloudflare Pages → Edit, plus Account Settings read if you need to list account IDs. Prefer least privilege. Export environment variables from a private secrets file:
export CLOUDFLARE_API_TOKEN="…"
export CLOUDFLARE_ACCOUNT_ID="…"
Confirm access with a harmless API call (fetch account details) before deploying. If you see 401/403, fix scopes before debugging Wrangler flags. Rotate tokens if they ever appear in screenshots, shared logs, or Drive sync folders.
Create or reuse a project
Project names become part of the default hostname: https://<project-name>.pages.dev. Pick a stable, lowercase, hyphenated name—for example negency-lab-pilot. Create the project in the dashboard or let the first wrangler pages deploy create it when you pass --project-name.
For direct uploads, each upload is a deployment. For a solo pilot, “latest production deployment” is enough mental model. Avoid renaming projects casually; URLs and runbooks drift when names change.
Deploy the folder
From the directory that contains index.html:
npx wrangler pages deploy . \
--project-name=negency-lab-pilot \
--commit-dirty=true
Wrangler uploads assets, prints a deployment URL, and exits non-zero on auth or quota errors. Capture the URL in your runbook. Useful habits: deploy only the publish directory (no node_modules, no .env, no secrets); keep ads.txt and privacy pages in the same root; use HTML comments like <!-- ADSENSE_SLOT: header --> until AdSense issues a real publisher ID.
If you use a monorepo, pass the path explicitly: wrangler pages deploy ./public --project-name=…. Double-check that index.html is at the root of whatever path you upload—otherwise you get a mysterious empty site.
Verify and iterate
- Open the printed pages.dev URL on mobile and desktop.
- Check /robots.txt, /sitemap.xml, /privacy.html, and one long article.
- View source: confirm no accidental API keys.
- Re-run the same deploy command after edits; global propagation is usually seconds to a minute.
Prefer fixing typos in source and redeploying over live dashboard edits. Add a lightweight checklist to your PR or commit message template: “privacy link works, sitemap lists new article, ad placeholders still comments only.”
Later: custom domain via CNAME
When content is ready, add a custom domain in Pages → Custom domains. If your DNS host requires leaving nameservers on a free provider (for example DNSHE), create a CNAME from your hostname (such as wei-huang.ccwu.cc) to <project>.pages.dev (or the target Cloudflare shows). Do not change NS to Cloudflare if that risks losing the free domain. Wait for SSL to become Active before treating the custom host as primary—or submit AdSense on pages.dev first if that matches your strategy.
Document both hostnames in the runbook during transition so Search Console and AdSense site URLs stay consistent with what you actually submit.
Troubleshooting
- Engine mismatch — “Wrangler requires Node.js v22” means switch runtimes (nvm/fnm).
- 401 Unauthorized — Token missing Pages Edit, wrong account, or expired token.
- Empty site — Deployed a parent folder without index.html at upload root.
- Old content — Hard-refresh; confirm you are on the production deployment.
- Asset 404s — Paths should be root-absolute (
/css/style.css) for multi-level article URLs.
Once this pipeline is boring, you have the right foundation: boring deploys are how content sites ship weekly articles without drama.
Recommended publish directory structure
Before the first deploy, normalize paths so article URLs and CSS never break:
/index.html
/about.html
/privacy.html
/contact.html
/robots.txt
/sitemap.xml
/ads.txt
/css/style.css
/articles/index.html
/articles/your-slug.html
Use root-absolute asset paths (/css/style.css) rather than relative ../css chains. Absolute paths survive URL nesting and custom domain switches. Keep a single stylesheet until you have a real design system need—extra CSS files are just more 404 opportunities on day one.
Exclude from upload: .env, secrets/, node_modules/, editor swap files, and any screenshots that contain tokens. A simple rsync or explicit folder path to Wrangler is safer than zipping the entire home directory.
Light automation without a full CI product
You do not need GitHub Actions on day one. A shell script that sources secrets, runs a link checker (even grep for unfinished TODO), and calls Wrangler is enough. When you later add Git, wrap the same script in Actions with secrets stored in the repo settings—not in the workflow YAML plaintext.
Version the content with Git even if deploys are direct upload. Git is for history; Wrangler is for publish. Mixing those concerns causes people to push broken drafts live or, worse, commit API tokens “just for the deploy step.”