Project structure
The Next.js + Convex monorepo layout and where to make changes to routes, data, schema, auth, the admin app, emails, jobs, MCP and tests.
The kit is a pnpm and Turborepo monorepo with four apps and one shared package. convex.json at the root points the Convex CLI at apps/database/convex/, so run all Convex commands from the repository root.
The four apps
| Package | Path | Role |
|---|---|---|
web | apps/web | Next.js 16 customer application and Fumadocs content. Dev server on port 3000 |
admin | apps/admin | Next.js 16 admin application: /app-admin, admin login, admin MCP consent, feedback and roadmap moderation. Dev server on port 3002 |
database | apps/database | Convex backend: schema, functions, MCP servers, tests. Exports database/api and database/data-model |
@nextbase/email | apps/email | 41 React Email templates and the preview server on port 3001 |
packages/typescript-config holds the shared tsconfig.base.json. The root scripts/ folder has the local bootstrap, verify-local.sh and the gh signoff wrapper. vercel.admin.json at the root is the Vercel configuration for the admin project. docs/separate-admin-app.md describes the admin split and impersonation; docs/mcp-connections.md is the MCP reference. AGENTS.md at the root and in each app is the hand-edited source for all generated AI tool configs (CLAUDE.md, GEMINI.md, .cursor/, .codex/ and so on); run pnpm agents:sync after editing it.
Where to make changes
Routes and pages
Web routes live in apps/web/src/app, grouped by access level:
(external-pages)/public marketing site: landing,blog/,changelog/,docs/,terms/.(dynamic-pages)/(login-pages)/for/loginand/sign-up;(misc-login-pages)/for password reset and the legacy auth callbacks.(dynamic-pages)/(authenticated-pages)/for the signed-in app:dashboard/,onboarding/,user/(settings, connections, notifications, invitations) and(application-pages)/workspace/[workspaceSlug]/andproject/[projectSlug]/. Layouts use@navbar/and@sidebar/parallel route slots.(dynamic-pages)/(updates-pages)/for feedback boards and the roadmap.api/route handlers:stripe/webhooks,invitations,chat,search,revalidate,health, OG images andv1/.auth/impersonate/receives impersonation handoffs from the admin app;mcp/authorize/is the user MCP consent page.
Admin routes live in apps/admin/src/app:
app-admin/(admin-pages)/withusers/,workspaces/,marketing/(blog,changelog,authors,tags),payment-gateway/,settings/,connections/and the@navbar/and@sidebar/slots. The layout callsunauthorized()for anyone without the admin platform role.(dynamic-pages)/(updates-pages)/for the admin views of feedback and the roadmap.login/,auth/callback/andmcp/authorize/for the admin's own sign-in and the Admin MCP consent page.api/admin/marketing/media/for media uploads.
Cache Components are on in both apps. Code that reads cookies, auth tokens or Convex data at request time must sit inside <Suspense>, and route params arrive as Promise<unknown>; parse them with the schemas in src/utils/zod-schemas/params.ts. The new-page skill in .agents/skills/ has the per-area checklist.
Server and data access
All data access goes through Convex functions. On the server, call fetchQuery or fetchMutation from convex/nextjs with { token: await convexAuthNextjsToken() }. In client components use useQuery, usePaginatedQuery and useMutation from convex/react. Import api from database/api and Id or Doc from database/data-model.
src/data/{user,admin,anon}/ in each app holds "use server" functions and server actions built with next-cool-action clients from that app's src/lib/safe-action.ts. apps/web/src/rsc-data/user/ holds React cache() wrappers for server components. Many files in src/data/ still import the Supabase clients; port them to Convex functions when you touch them and do not add new code on src/supabase-clients/ or apps/web/src/middlewares/.
Admin publication actions call the web app's POST /api/revalidate with the shared REVALIDATE_SECRET so the customer app refreshes its public content caches.
Schema and migrations
Convex has no SQL migrations. Edit apps/database/convex/schema.ts, then pnpm convex:dev or pnpm convex:once pushes it. The push fails if existing documents violate the new schema, so make breaking changes in steps: add fields as v.optional(...), backfill with an internal mutation, then tighten. Run pnpm convex:codegen and commit apps/database/convex/_generated/. When seed data changes, update convex/seed.ts (bump SEED_VERSION) and seed.test.ts. The db-migration skill describes this flow.
apps/database/supabase/ (SQL migrations, pgTAP tests) and apps/database/lib/database.types.ts are legacy. Don't add to them.
Authentication and authorization
Providers and callbacks are in convex/auth.ts; the issuer is convex/auth.config.ts; the redirect allowlist is convex/lib/auth_redirect.ts. Authorization helpers are in convex/lib/authz.ts and must be the first line of every public function. Operator-only functions such as platform:grantAdmin (convex/platform.ts) are internalMutations. The Next.js gates are apps/web/src/proxy.ts and apps/admin/src/proxy.ts. Details are on the authentication page.
Emails
Templates are React components in apps/email/emails/; preview them at http://localhost:3001 with pnpm dev:email. Auth emails are sent by the backend in convex/auth.ts through the Resend API. Application emails from the Next.js server go through apps/web/src/utils/api-routes/utils.ts, which uses Mailpit in development and Resend otherwise.
Background jobs and crons
convex/crons.ts registers the hourly MCP credential prune. Add more jobs there with crons.interval or crons.cron, pointing at internal functions. The schema also has integrationJobs, webhookEvents and idempotencyKeys tables for retryable work. @convex-dev/migrations, @convex-dev/aggregate and @convex-dev/rate-limiter are installed but not registered in convex.config.ts; add app.use(...) before importing them.
MCP
Everything is under apps/database/convex/mcp/, with the consent UI in src/components/mcp-consent.tsx of each app (user consent in web, admin consent in admin). See agent connections.
Tests
| Kind | Where | Command |
|---|---|---|
| Convex functions | apps/database/convex/*.test.ts, Vitest with convex-test on the edge runtime | pnpm --filter database test |
| Web units | colocated *.test.ts(x) under apps/web/src, Vitest with jsdom and Testing Library | pnpm --filter web test |
| Admin units | colocated *.test.ts(x) under apps/admin/src | pnpm --filter admin test |
| End to end | apps/web/e2e/{anon,user,admin,...}/*.spec.ts, Playwright config in apps/web/playwright/ | pnpm test-integration after a build |
| Everything before handoff | lint, typecheck, unit tests, compile-mode build | pnpm verify:local |
Code style is Oxlint and Oxfmt (pnpm lint:fix), not ESLint, Prettier or Biome.