Convex backend
The default webapp backend (--db convex). The idiom is reactive: components call useQuery and re-render automatically when data changes — no refetch calls, no cache invalidation. Mutations are thin handlers that derive identity server-side.
The reactive idiom
- useQuery(api.<domain>.<fn>) returns undefined while loading — render Skeleton then; EmptyState when the result is empty
- Every handler calls requireUserId(ctx) FIRST — identity is always server-derived, never a client argument
- One convex/<domain>.ts per domain, shaped like convex/items.ts: thin handlers, for...of + await for batch db ops (never forEach(async ...))
- Convex v.optional(...) fields must be omitted, not set to undefined (exactOptionalPropertyTypes) — build objects with conditional spreads
- Domain tables live in convex/schema.ts beside ...shellTables; always index("by_user", ["userId"]) for per-user data; never name an index by_id (reserved)
The .env.local rule (critical)
apps/<slug>/.env.local records which Convex cloud project this app belongs to. NEVER delete it, and NEVER run convex dev --configure=new on an app that has one — either action mints a duplicate cloud project. The deploy script guards this; do not bypass the guard.
First run in a fresh app: npx convex dev --once creates the deployment, writes .env.local, and generates types. From then on the recorded project is always reused — including by pnpm deploy.
Codegen ordering
convex codegen needs a configured deployment, so the order on a fresh app is strict:
cd apps/<slug>
npx convex dev --once # 1. configure deployment + first codegen
npx convex codegen # 2. after every backend change
pnpm exec tsc --noEmit # 3. fix to zero errors
Run steps 2 and 3 after every schema or function change; a green tsc is the merge gate.
Seeding and prod
npx convex run <domain>:seed '{}' # dev deployment
npx convex run <domain>:seed '{}' --prod # after pnpm deploy
npx convex env set ANTHROPIC_API_KEY ... --prod # prod env vars