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/intosite/.
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):
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 wrangler — not from
Cloudflare's git integration. This lets us gate the deploy on CI being green.
One-time setup
-
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.
-
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.
-
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).
-
(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).