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
| Field | Value |
|---|---|
duncan.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:
| Approach | Result |
|---|---|
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 policy | Correct — 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)
- Open Cloudflare dashboard → Workers & Pages → listing-lens-docs
- Go to Settings → General
- Click Enable access policy
- Click Manage on the created policy
- In Zero Trust, edit listing-lens-docs - Cloudflare Pages:
- Allow policy:
duncan.andrew.haywood@gmail.comonly - Identity provider: One-time PIN
- For production
listing-lens-docs.pages.dev: remove the wildcard (*) from the Subdomain field (known issues)
- Allow policy:
- 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:
| Secret | Description |
|---|---|
CLOUDFLARE_API_TOKEN | API token with Cloudflare Pages + Zero Trust edit permissions |
CLOUDFLARE_ACCOUNT_ID | Cloudflare 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:
| Variable | Example |
|---|---|
ACCESS_DOMAIN | https://math-450-pages.cloudflareaccess.com |
ACCESS_AUD | Application 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.jsonfor documentation and scripts - Keep
CLOUDFLARE_API_TOKENin CI secrets only