# Deploying a site to Cloudflare Pages

How to put a static or Next.js site on Cloudflare Pages via `wrangler`, the way `realagent.pages.dev` is deployed. This covers the build → deploy → verify loop and the Next.js specifics. Auth is a minted narrow token per `api.md`; if the site also captures form data, read `d1-data-capture.md` for the dual Pages-Edit **and** D1-Edit scope.

This is distinct from `references/serving-published-sites.md`, which serves a platform-published site at a custom domain through the brand's **cloudflared tunnel**. This reference is about **Cloudflare Pages** — Cloudflare hosts the build, not the install device.

---

## When to use Pages vs the tunnel

- **Cloudflare Pages** (this reference) — a standalone marketing/landing/app site that Cloudflare builds and serves on its edge, optionally with Pages Functions + D1. Use when the site is its own project and you want Cloudflare to host it.
- **The brand tunnel** (`manual-setup.md` + `serving-published-sites.md`) — expose something running on the install device at a custom domain. Use when the content is served by the platform itself.

---

## 1. Build the site

Produce the static output the framework emits.

- **Static / Vite / Astro:** `npm run build` → output in `dist/` (or the framework default).
- **Next.js:** Pages needs a static or edge-compatible build. For a fully static site use `output: "export"` in `next.config.js` and `npm run build` → output in `out/`. For a Next.js app with server routes, deploy through the Pages adapter (`@cloudflare/next-on-pages`): `npx @cloudflare/next-on-pages` → output in `.vercel/output/static`.

## 2. Configure `wrangler.toml`

```toml
name = "<project>"                       # the Pages project name, e.g. realagent
pages_build_output_dir = "<output-dir>"  # dist | out | .vercel/output/static
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]  # required for next-on-pages and most Functions
```

If the site captures form data, add the `[[d1_databases]]` binding from `d1-data-capture.md` here.

## 3. Mint a deploy token

A Pages deploy needs **Account · Cloudflare Pages · Edit**. If the site has Pages Functions hitting D1, the token also needs **Account · D1 · Edit** (the single most common breakage — see `d1-data-capture.md`). Mint it per `api.md` § Minting a narrow token:

```bash
set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
# MINTED = a narrow token scoped to Pages Edit (+ D1 Edit if the site uses D1)
```

## 4. Deploy

```bash
CLOUDFLARE_API_TOKEN="${MINTED}" wrangler pages deploy <output-dir> \
  --project-name <project> --branch=main
```

`--branch=main` deploys to the production alias (`https://<project>.pages.dev`); any other branch name produces a preview URL instead. The first deploy of a new project name creates the project. The token value is never printed — `wrangler` prints the deployment URL, which is what gets surfaced.

## 5. Custom domain (optional)

Attach a custom domain to the Pages project in the dashboard (**Workers & Pages → your project → Custom domains → Set up a domain**), or via the API. Cloudflare provisions the certificate and the CNAME automatically when the zone is on the same account.

---

## Outcome contract

Hosting is done when the deployed URL serves the **new** build:

```bash
curl -s "https://<project>.pages.dev/" | head
```

The response showing the new markup is the proof. A successful `wrangler` exit alone is not the contract — the live URL returning the new content is. Surface the operation as verb + target ("deploying Pages project `realagent`"); the token value never appears in chat or stdout.
