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