Cloudflare Pages gives you free, git-connected hosting for static sites and
static-generator output (Hugo, Astro, Next.js static export, plain HTML).
Push to main, get a deploy. Push to a branch, get a preview URL. This
walkthrough covers the GitHub integration end to end: connecting the repo,
build configuration, environment variables, custom domains, and preview
deployments.
Prerequisites
- A Cloudflare account (free tier is sufficient)
- Your site’s source in a GitHub repository
- A build command and output directory, if you’re using a static site generator (Hugo, Astro, Eleventy, etc.) — skip this if you’re deploying plain HTML/CSS/JS
Step 1: connect the GitHub repository
- Log into the Cloudflare dashboard and select Workers & Pages from the left sidebar.
- Click Create application → Pages tab → Connect to Git.
- Authorize Cloudflare’s GitHub App if you haven’t already. You’ll be asked to grant access to either all repositories or a selected list — choose selected repositories and pick only what you need. Don’t grant blanket access to your whole GitHub account for a single site.
- Select the repository and branch you want to deploy from (usually
main).
Step 2: configure the build
Cloudflare will try to auto-detect your framework. Verify or set:
| Setting | Example (Hugo) | Example (plain HTML) |
|---|---|---|
| Build command | hugo --minify | (leave blank) |
| Build output directory | public | / or your root |
| Root directory | / (or subfolder if monorepo) | / |
If you’re using Hugo, also set the HUGO_VERSION environment variable
(see Step 3) — otherwise Cloudflare uses a default version that may
not match what you built and tested locally, and Hugo’s breaking changes
between minor versions are common enough that this bites people.
Framework presets exist for most common generators (Hugo, Astro, Next.js,
Eleventy, Gatsby, etc.) and will pre-fill the build command and output
directory — check them, but don’t assume they match your project’s
actual config, especially if you’ve customised config.toml / hugo.yaml
output paths.
Step 3: environment variables
Under Settings → Environment variables, set anything your build needs:
HUGO_VERSION(orNODE_VERSION, etc.) pinned to what you use locally- Any API keys or tokens your build step needs — Cloudflare encrypts these at rest
- Separate values for Production and Preview environments if needed — useful if preview deploys should hit a staging API
One trap: most generators inline build-time environment variables into
the shipped JavaScript. Anything client-side code reads from
process.env.* at build time ends up in the public bundle, encryption
at rest notwithstanding. Only put values here that you’d be comfortable
seeing in view-source.
Step 4: deploy
Click Save and Deploy. Cloudflare clones the repo, runs the build
command, and publishes the output directory to a *.pages.dev subdomain.
First build typically takes 30–90 seconds depending on the generator
and content volume.
Every subsequent push to the connected branch triggers a new deployment automatically — no webhook or Action to maintain on your end.
Step 5: preview deployments
The killer feature, and it’s on the free tier. Every non-main branch and
every pull request against the connected repo gets its own unique preview
URL (<hash>.<project>.pages.dev), built the same way as production.
Cloudflare posts a comment on the PR with the preview link if you’ve
enabled GitHub integration comments.
Practical use: push content or template changes to a branch, get a live
URL to review before merging to main, without spinning up a staging
server yourself.
Step 6: custom domain
- Go to your Pages project → Custom domains → Set up a custom domain.
- Enter the domain or subdomain.
- If the domain’s DNS is already on Cloudflare, the CNAME record is added automatically. If not, you’ll be given a CNAME target to add at your existing DNS provider.
- SSL is provisioned automatically (usually within a few minutes) — no separate certificate step.
For a subdomain (e.g. blog.example.com), this is a single CNAME. For an
apex/root domain, Cloudflare handles it via CNAME flattening if the zone
is on Cloudflare DNS — you don’t need an A-record workaround.
Step 7: build caching and rollbacks
- Cloudflare caches dependencies between builds (npm / yarn / Hugo modules) automatically — no config needed. But Retry deployment can sometimes serve from a stale cache after a broken build; if a build behaves unexpectedly, use Delete cache and retry rather than assuming it’s a code issue.
- Every deployment is retained and rollback-able from the Deployments tab — click any prior successful deployment and Rollback to this deployment. This is instant (it repoints the routing; it doesn’t rebuild), which makes it a reasonable substitute for a staging gate on low-stakes content sites.
Common gotchas
- Wrong output directory is the most common first-deploy failure — double-check it matches your generator’s actual output folder, not the framework preset’s guess.
- Case-sensitive paths. Cloudflare Pages serves from a Linux-based environment; a build that works on macOS (case-insensitive filesystem) can 404 on asset paths that differ only in case.
- Build minutes are metered on the free tier (500 builds/month at time of writing) — fine for a single site, worth watching if you’re managing several from one account.
Where this fits
For a portfolio of static and content sites, Cloudflare Pages removes a category of operational overhead entirely: no VPS to patch, no Caddy / Nginx config to maintain for that specific site, no SSL renewal to track. It’s a reasonable default for anything that’s pure static output. Keep the VPS-hosted stack for anything running server-side application logic — Flask / Django APIs, background workers, direct database access — where Pages Functions or Workers would be a heavier lift than a straightforward long-running process.