Appearance
Deployment
This monorepo deploys two separate front-ends to Cloudflare Pages, both connected to this GitHub repository. Each Pages project has its own dashboard build configuration. Cloudflare Pages only reads a single wrangler.toml at the repo root, and that file belongs to the members portal — so the admin app's settings live only in its Pages dashboard. This document is the source-of-truth for both so the dashboards can be reproduced or audited.
The repo is a pnpm workspace (
pnpm-workspace.yaml,packageManager: pnpm@11.2.2in the rootpackage.json). Every Pages build must install from the repo root so the workspace resolves; setting a project's "Root directory" to a sub-app breaks the install.
Project 1 — yourpeople (Members Portal)
- App:
apps/members(@yourpeople/members), Nuxt 4 + Nitrocloudflare-pagespreset. - Repo config: root
wrangler.toml(name = "yourpeople-members",pages_build_output_dir = "apps/members/dist",compatibility_flags = ["nodejs_compat"]). Pages reads this on every deploy.
| Setting | Value |
|---|---|
| Root directory | / |
| Build command | pnpm install --frozen-lockfile && pnpm --filter @yourpeople/members build |
| Build output directory | apps/members/dist |
| Node version | 22 (NODE_VERSION=22) |
Production + Preview env vars: NUXT_SESSION_PASSWORD (rotating 32-byte random — never commit), NUXT_API_BASE, NUXT_PUBLIC_API_BASE. See apps/members/README.md for the full rationale (incl. why nodejs_compat is required).
Required environment variables (summary)
Members portal (
@yourpeople/members/apps/members):NUXT_SESSION_PASSWORD— strong secret (>=32 chars). Set as a Pages secret for Production and Preview.NUXT_API_BASE— server-side base URL for the .NET API (host root, no trailing/api). Must be reachable from Pages.NUXT_PUBLIC_API_BASE— client-side API base (same asNUXT_API_BASEin most setups).
Admin portal (
@yourpeople/web/apps/web):VITE_API_BASE_URL— base URL used by the SPA for API requests (exposed to client bundles). Set in Pages envs for Production/Preview.VITE_CLERK_PUBLISHABLE_KEY— Clerk publishable key (if using Clerk).
Internal docs (
@yourpeople/internal-docs/apps/internal-docs): none required.
Additional deploy checklist
- Ensure Cloudflare Pages project Root directory is
/and the build command runs from the repo root (use the--filterpnpm flag to target the specific workspace). Examples in the top-levelDEPLOYMENT.md. - Node version: set
NODE_VERSION=22in Pages. - Secrets management: add keys in the Pages UI under Environment variables → Variables and secrets. Never commit production secrets to the repository.
- If you rely on external services (Clerk, API, 3rd-party webhooks), confirm the service endpoints and webhook URLs are configured with the correct Pages domain (preview vs production) and that any required webhook secret is provided to the service.
Project 2 — yourpeopleapp (Admin Portal)
- App:
apps/web(@yourpeople/web), Vue 3 + Vite SPA (static output, no Worker —nodejs_compatnot needed). - Repo config: none. The root
wrangler.tomlbelongs to the members project, so this project is driven entirely by its dashboard settings below.
| Setting | Value |
|---|---|
| Root directory | / |
| Build command | pnpm install --frozen-lockfile && pnpm --filter @yourpeople/web build |
| Build output directory | apps/web/dist |
| Node version | 22 (NODE_VERSION=22) |
Why this project has been failing: it predates the pnpm-workspace conversion (PR #18) and its dashboard build command still runs an
npm-based install againstapps/web. With no usable rootpackage.jsonfor npm andapps/webnow a workspace member, that install fails before any build step. Updating the build command and root directory to the values above fixes it.
If the admin app does not need its own public Pages deployment, delete this project instead — that removes a perpetually-red status check and the duplicate deploy webhooks on every PR.
Project 3 — yourpeople-internaldocs (Internal Docs)
- App:
apps/internal-docs(@yourpeople/internal-docs), VitePress static site. Pure static output — no Worker, nonodejs_compat. - Repo config: none. The root
wrangler.tomlbelongs to the members project, so this project is driven entirely by its dashboard settings below. VitePress emits toapps/internal-docs/.vitepress/dist(srcDir: '.',outDir: '.vitepress/dist'inapps/internal-docs/.vitepress/config.mts).
| Setting | Value |
|---|---|
| Root directory | / |
| Build command | pnpm install --frozen-lockfile && pnpm --filter @yourpeople/internal-docs build |
| Build output directory | apps/internal-docs/.vitepress/dist |
| Node version | 22 (NODE_VERSION=22) |
No production/preview env vars required.
Root-directory gotcha: a failed deploy showed
Cannot find cwd: /opt/buildhome/repo/apps/internals-docs— two mistakes in one. The folder isapps/internal-docs(singular "internal"), and "Root directory" must be/, not the sub-app. With the v2 root directory strategy (see the deploy log), the build output directory is resolved relative to the repo root, so it stays the fullapps/internal-docs/.vitepress/dist. The--filterflag in the build command is what targets the app — never move the root intoapps/internal-docsto do it.
Notes
- Do not set "Root directory" to
apps/members,apps/web, orapps/internal-docs. Pages clones the whole repo; the build command reaches into the target app via the--filterflag. A sub-directory root breaks pnpm-workspace resolution and fails on branches that don't contain that directory. - The GitHub Actions workflow (
.github/workflows/members-ci.yml) is a separate CI surface from these Pages deploys; it lints, type-checks, tests and builds both apps plus the .NET API on every push.