Overview
Cloudflare Pages is where I deploy almost every static React project. It’s free for hobby use, ships your build to a global CDN, gives every pull request a unique preview URL, and rolls back to any prior deploy in a click. If you scaffolded with create-react-adam — or any Vite-based React app — the build output drops in cleanly with no plugins.
This guide is the static-site path: connect a repo, Cloudflare builds and serves the artifact. Plus the one config detail that bites everyone: SPA routing fallback.
Official docs to keep open:
Prerequisites
- A Cloudflare account (free tier is fine)
- A Vite + React project that builds locally with
npm run build - A GitHub or GitLab repo
Connect the repo
One-time setup, then every push deploys automatically.
-
Open the Cloudflare dashboard and go to Workers & Pages → Create → Pages → Connect to Git.
-
Authorize Cloudflare on your GitHub or GitLab account, then pick the repository.
-
Configure the build:
Setting Value Framework preset ViteBuild command npm run buildBuild output dir distRoot directory /(leave default) -
Under Environment variables, add
NODE_VERSIONset to whatever your.nvmrcsays (create-react-adam ships24.15.0). Without this, Cloudflare may pick an older Node and your build will fail. -
Click Save and Deploy. The first build takes 1–2 minutes. Subsequent builds are usually under a minute.
Every push to your default branch updates production. Every PR or branch push gets a unique preview URL like https://abc123.my-app.pages.dev — great for sharing work in progress.
SPA routing — the _redirects file
This is the gotcha. Wouter and React Router both render routes client-side, so a request for /about only works if index.html is served. Without a fallback, a hard refresh on /about returns a Cloudflare 404.
Fix: add a public/_redirects file with a single line.
/* /index.html 200
Vite copies everything in public/ into dist/ verbatim, so the file ends up at the deploy root. Cloudflare reads it and rewrites every unmatched path to index.html with a 200 status. Hard refresh on /about now works.
Full syntax: Cloudflare redirects docs.
Custom domain
- In the Pages project, go to Custom domains → Set up a custom domain.
- Enter your domain (e.g.,
app.example.com). - If the domain is already on Cloudflare, the DNS record is created automatically. If not, follow the displayed instructions to add a
CNAME. - Cloudflare provisions a TLS certificate within a few minutes.
Step-by-step with screenshots: Custom domains docs.
Preview deployments and rollbacks
- Preview deployments — every branch and PR gets its own URL. Share it with reviewers without waiting for a merge.
- Rollbacks — under Deployments, click any prior build and Rollback to this deployment. Bad release reverted in seconds, no rebuild needed.
Common Issues
- 404 on refresh — missing
public/_redirects. See the SPA section above. - Build fails with “command not found” — Node version mismatch. Set
NODE_VERSIONenv var to match.nvmrc. - Build fails with “no such file” — Build output directory is wrong. Vite writes to
dist, notbuild. - Environment variables undefined at runtime — Vite only exposes vars prefixed with
VITE_to the browser. Set them under Settings → Environment variables → Production / Preview. - Slow first paint on a fresh deploy — usually fine after the edge cache warms up. If not, check the Cloudflare Cache Rules.
Why this pairs well with create-react-adam
The CI workflows that ship with create-react-adam — tsc --noEmit, format:check, lint:check, npm run build, plus the Playwright e2e suite — run on GitHub Actions before Cloudflare ever sees the commit. By the time Pages picks up the push, the build is already known good. Cloudflare’s job is just to ship the artifact.