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

  1. Log into the Cloudflare dashboard and select Workers & Pages from the left sidebar.
  2. Click Create applicationPages tab → Connect to Git.
  3. 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.
  4. 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:

SettingExample (Hugo)Example (plain HTML)
Build commandhugo --minify(leave blank)
Build output directorypublic/ 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 (or NODE_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

  1. Go to your Pages project → Custom domainsSet up a custom domain.
  2. Enter the domain or subdomain.
  3. 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.
  4. 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.