Skip to content

Fennec Local Development

How to bring fennec up locally for development. Two halves: the Vite SPA and the embedded Strapi 5 backend.

Prerequisites

  • Node.js 22 (matches .github/workflows/fennec.yml NODE_VERSION).
  • pnpm 10.26.0 (matches the workflow PNPM_VERSION). The repo has both a pnpm-lock.yaml and a stray package-lock.jsonalways use pnpm per apps/fennec/CLAUDE.md.
  • Docker (for the Strapi backend, Postgres, and Redis via the embedded docker-compose.yml / docker-compose-local.yml under apps/fennec/).
  • A Clerk dev instance with a publishable key.
  • Network access to a running brain dev instance (or stub).

Initial setup

Terminal window
cd apps/fennec
pnpm install --frozen-lockfile

Create apps/fennec/.env (or copy from a teammate’s known-good values). Required values (see fennec-env.md for the full table):

REACT_APP_CLERK_PUBLISHABLE_KEY=pk_test_...
REACT_APP_BACKEND_URL=http://localhost:9950
REACT_APP_BRAIN_URL=http://localhost:3001
REACT_APP_R2BUCKET_NAME=tenderloin-dev
REACT_APP_FENNEC_API_TOKEN=<shared dev token>

Important: Vite is configured with envPrefix: "REACT_APP_" (apps/fennec/vite.config.ts). Variables without that prefix are not exposed to the SPA.

Run the SPA

Terminal window
cd apps/fennec
pnpm dev # alias for `vite` — runs on http://localhost:3000

Vite serves on 0.0.0.0:3000 (server.host: true) so containers and other devices on the LAN can reach it. The dev server has no proxy block — upstream URLs come straight from REACT_APP_BACKEND_URL and REACT_APP_BRAIN_URL, so make sure CORS on those upstreams allows http://localhost:3000.

The repo’s top-level make dev-up-d brings up the full stack including fennec; use it when you need the whole platform. Use the per-app pnpm dev when iterating on fennec only.

Run the Strapi backend (mock / embedded)

The “backend” the SPA talks to (REACT_APP_BACKEND_URL) is a Strapi 5 project at apps/fennec/backend/. It is managed via docker-compose:

Terminal window
cd apps/fennec
docker compose -f docker-compose-local.yml up -d

This brings up Strapi (default :9950), Postgres, and Redis with values from apps/fennec/backend/.env.

Mocking just brain — if you do not need real generations data, point REACT_APP_BRAIN_URL at a local mock server. There is no first-class mock backend bundled with fennec today (TODO(@law): confirm with team if a preferred mock exists; otherwise standard practice is to run brain locally or use a fixture-based mock service).

Quality gates

Match what CI runs:

Terminal window
pnpm lint # ESLint, --max-warnings 0
pnpm tsc # tsc --noEmit

Both must pass before pushing. Additional helpful commands:

Terminal window
pnpm lint:fix # autofix lint
pnpm format:all # Prettier across src
pnpm format:staged # Prettier on staged files only
pnpm knip # dead-code detection
pnpm knip:exports # unused exports
pnpm build # production build (vite build → dist/)
pnpm preview # serve the production build locally

Auth in local dev

Sign-in uses Clerk. Two practical setups:

  1. Real Clerk dev instance. Set REACT_APP_CLERK_PUBLISHABLE_KEY to the dev publishable key. Brain must use the matching CLERK_SECRET_KEY so tokens issued by your SPA are accepted.
  2. Shared team Clerk dev. If the team uses a shared dev Clerk environment, copy that key from your teammate or 1Password.

Roles are enforced both by PrivateRoute (apps/fennec/src/components/PrivateRoute.tsx) and brain. To reach admin-only pages locally, your brain users row must have role set to ADMIN or ROOT — see apps/fennec/src/lib/auth/roles.ts.

Common local issues

SymptomFix
apiConfig.brain is undefined in DevToolsREACT_APP_BRAIN_URL missing — set it and restart pnpm dev (env values are read at startup)
CORS errors hitting localhost:3001Brain CORS allowlist missing http://localhost:3000
Cookies not stuck after sign-inwithCredentials requires brain to send Access-Control-Allow-Credentials: true with explicit origin
Port 3000 already in useKill the holder, or run vite --port 3001 and update Clerk allowed origins
Strapi container won’t startdocker compose down -v then bring it back up; check apps/fennec/backend/.env for typos
ESLint flagging files you didn’t changePull latest base configs (eslint.config.base*.mjs at repo root)
pnpm install slow / wrong lockfileTwo lockfiles exist — never use npm install; always pnpm install --frozen-lockfile

Day-to-day workflow

  1. pnpm dev in apps/fennec/.
  2. Code changes hot-reload via Vite.
  3. Before committing: pnpm lint && pnpm tsc.
  4. Open a PR; .github/workflows/fennec.yml re-runs lint + typecheck.
  5. After merge, Railway deploys (see fennec-runbook.md).