← Back to docs home

Deployment

This site is a Next.js static export deployed to Cloudflare Pages and protected by Cloudflare Access.

Access is restricted to a single email allowlist. No other identities can reach the docs.

Live URL

https://listing-lens-docs.pages.dev

Allowed identity

FieldValue
Emailduncan.andrew.haywood@gmail.com

Architecture

Browser → Cloudflare Access (email gate) → Cloudflare Pages (static Next.js export)

Cloudflare Access runs at the edge before any page is served. Unauthenticated visitors are prompted to authenticate via Cloudflare Access (one-time PIN to the whitelisted email). Only duncan.andrew.haywood@gmail.com can complete authentication.

Important: pages.dev Access cannot be enabled via Zero Trust API

*.pages.dev is a Cloudflare-shared domain. Creating a self-hosted Access application in Zero Trust (via API or dashboard) for listing-lens-docs.pages.dev does not work:

ApproachResult
Self-hosted ZT app via API (domain field)Ignored — site stays public (HTTP 200)
Self-hosted ZT app via API (destinations field)Broken — HTTP 500 for everyone
Pages Settings → Enable access policyCorrect — login redirect works

Access must be enabled from Pages project settings, which creates a listing-lens-docs - Cloudflare Pages application linked to the project.

One-time dashboard setup (required)

  1. Open Cloudflare dashboardWorkers & Pageslisting-lens-docs
  2. Go to SettingsGeneral
  3. Click Enable access policy
  4. Click Manage on the created policy
  5. In Zero Trust, edit listing-lens-docs - Cloudflare Pages:
    • Allow policy: duncan.andrew.haywood@gmail.com only
    • Identity provider: One-time PIN
    • For production listing-lens-docs.pages.dev: remove the wildcard (*) from the Subdomain field (known issues)
  6. Verify:
export CLOUDFLARE_API_TOKEN=...
export CLOUDFLARE_ACCOUNT_ID=...
export DOCS_HOSTNAME=listing-lens-docs.pages.dev
./docs-site/scripts/setup-cloudflare-access.sh

Expected: HTTP 302 (login redirect), not 200 (public) or 500 (broken).

Local development

cd docs-site
npm install
npm run dev

Local dev does not enforce Access. Production is protected at the Cloudflare edge.

Production build

cd docs-site
npm run build

Static files are written to docs-site/out/.

Deploy

GitHub Actions (recommended)

Push to main triggers .github/workflows/deploy-docs.yml when docs-site/** changes.

Required repository secrets:

SecretDescription
CLOUDFLARE_API_TOKENAPI token with Cloudflare Pages + Zero Trust edit permissions
CLOUDFLARE_ACCOUNT_IDCloudflare account ID

Manual deploy

cd docs-site
npm run pages:deploy

Cloudflare Access scripts

docs-site/scripts/setup-cloudflare-access.sh:

  • Ensures the Access App Launcher exists (needed for OTP login)
  • Removes stale manual API apps that break pages.dev
  • Verifies whether Access is active (expects 302/403, not 200/500)
  • Prints dashboard instructions if not yet configured

Do not create self-hosted Access apps for *.pages.dev or *.workers.dev via API. For Workers, use Settings → Domains & Routes → Enable Cloudflare Access instead.

Optional: Pages Functions JWT validation

After enabling Access in the dashboard, you can add defense-in-depth JWT validation by setting these Pages environment variables:

VariableExample
ACCESS_DOMAINhttps://math-450-pages.cloudflareaccess.com
ACCESS_AUDApplication AUD from the listing-lens-docs - Cloudflare Pages app

The functions/_middleware.ts handler validates JWTs when these vars are set.

Security notes

  • Access policies are not stored in the static site bundle
  • The email whitelist lives in infra/access-policy.json for documentation and scripts
  • Keep CLOUDFLARE_API_TOKEN in CI secrets only