Skip to content

Cloudflare Pages

Cloudflare Pages hosts the static SPA frontend. There are two separate Pages projects:

  • The app, serving the production SPA built from this repo.
  • The docs site (this MkDocs knowledge base), built from docs/ into site/.

They share a Cloudflare account but are independent projects with independent build configs and access rules.

App project

Build

Setting Value
Build command npm run build
Output directory dist
Node version 20.x
Root directory (repo root)

npm run build runs vite build (see package.json:7-9). The Vite config (vite.config.ts) emits compressed assets (gzip + brotli), a PWA service worker (via vite-plugin-pwa), and a set of manual chunks for vendor code (vite.config.ts:93-144).

Branch deployments

  • main → production deployment, served from the primary domain.
  • Any PR / feature branch → preview deployment with a Cloudflare-generated URL (<branch>.<project>.pages.dev). Preview URLs point at the same Supabase project as production unless the build-time env vars are overridden per environment.

Preview builds hit the production Supabase

Because VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY are baked in at build time, PR previews authenticate against whatever Supabase project the Pages env is pointing at. Do not run destructive QA against a preview URL unless you have configured a separate staging Supabase project in the Pages environment settings.

Required build-time environment variables

The frontend is a static SPA; every runtime env var must be known at build time (prefix VITE_ to be exposed by Vite to the bundle).

Variable Required? Source
VITE_SUPABASE_URL Yes Supabase dashboard
VITE_SUPABASE_PUBLISHABLE_KEY Yes Supabase dashboard (anon key)
VITE_SUPABASE_PROJECT_ID Optional Supabase dashboard
VITE_SENTRY_DSN Prod only Sentry project settings
VITE_APP_VERSION Optional Set by CI from the release tag
VITE_VAPID_PUBLIC_KEY If push notifications are enabled npx web-push generate-vapid-keys
VITE_TURNSTILE_SITE_KEY If Turnstile is used on any form Cloudflare Turnstile dashboard
VITE_GOOGLE_CLIENT_ID For Google Drive uploads Google Cloud OAuth client
VITE_GOOGLE_DRIVE_FOLDER_ID For Google Drive uploads Google Drive
VITE_RESEND_FROM Informational (server uses its own) Email template

See .env.example and env.example for the authoritative list as of the last repo change.

Do not put secrets in VITE_*

Anything prefixed VITE_ ends up in the JS bundle and is readable by every visitor. Only place public identifiers here (anon keys, public site keys, DSNs). Service-role keys, API secrets, and HMAC secrets belong in Supabase function secrets — see docs/secrets-setup.md.

Analyzing bundle size

Set ANALYZE=true before a build to emit dist/bundle-stats.html (vite.config.ts:13 + :36-42):

ANALYZE=true npm run build

Budgets are enforced in CI by scripts/check-bundle-budgets.mjs — see docs/operations/bundle-budgets.md.

Docs project

The docs site is served from a separate Cloudflare Pages project named alhuda-docs, deployed from GitHub Actions via wranglernot from Cloudflare's git integration. This lets us gate the deploy on CI being green.

One-time setup

  1. Create the Pages project (once, in the Cloudflare dashboard):

    • Pages → Create project → Direct Upload (not "Connect to Git").
    • Project name: alhuda-docs. This name is referenced by the GH Actions workflow (--project-name=alhuda-docs).
    • Leave the project empty; the first real deploy comes from GH Actions.
  2. Create a scoped API token (Cloudflare → My Profile → API Tokens):

    • Template: Custom token.
    • Permissions: Account → Cloudflare Pages → Edit.
    • Account resources: your account only.
    • Copy the token.
  3. Add two repository secrets (GitHub → Settings → Secrets → Actions):

    • CLOUDFLARE_API_TOKEN — the token from step 2.
    • CLOUDFLARE_ACCOUNT_ID — your Cloudflare account ID (Cloudflare dashboard → right sidebar → Account ID).
  4. (Optional) Custom domain + Access policy — Pages → alhuda-docs → Custom domains, then Zero Trust → Access → Applications (see below).

How a deploy happens

Configured in .github/workflows/ci.yml:

Job Triggers Runs after What it does
docs-build PR or push to main / demo ci + e2e both pass (e2e may be skipped) Builds MkDocs into site/, uploads as workflow artifact
docs-deploy Push to main only docs-build passes wrangler pages deploy site --project-name=alhuda-docs --branch=main

Why not Cloudflare git integration

Git integration deploys on every push, regardless of CI state. The explicit GH Actions path lets us gate the deploy on lint + tests + e2e + bundle budgets all passing. If any of those fail, the docs don't redeploy.

Build settings (inside the Pages project)

Because we deploy via Direct Upload, the project doesn't run its own build. The settings page will show no build command — that's intentional. If the Pages project is ever switched to git integration, use:

Setting Value
Build command pip install -r docs/requirements.txt && mkdocs build
Output directory site
Python version 3.11 (set via PYTHON_VERSION env var)

Cloudflare Access (email allowlist)

Because the knowledge base contains internal runbooks, permission matrices, and secret-setup guides, the docs Pages project is gated by Cloudflare Access. Configure an Access application scoped to the docs domain with:

  • Policy: Allow email (one-time PIN or IdP).
  • Allowlist: team @alhuda.co.in addresses plus any contractors currently active.

Access is the only thing protecting the docs

There is no secondary auth in MkDocs itself. If Access is misconfigured or the policy is set to "bypass" during a config change, the docs become public. Treat Access-policy edits like a production deploy — peer-review and test with an incognito session before closing the ticket.

Operational notes

  • Rollbacks. Cloudflare Pages keeps every deployment; roll back via the Pages dashboard → Deployments → ... → "Rollback to this deployment". Instant, no rebuild.
  • Custom domains. Managed in the Pages project → Custom domains. DNS is auto-wired when the zone is in the same Cloudflare account.
  • Logs. Build logs are in the Pages dashboard; runtime (edge) logs for a static SPA are minimal — errors surface via Sentry instead (see Sentry).