Project structure
The monorepo layout of the Next.js + Supabase starter, including the separate admin app, and where to make each kind of change.
The kit is a Turborepo workspace managed with pnpm. pnpm-workspace.yaml includes apps/* and packages/*. Root scripts in package.json fan out through turbo.json.
Repository tree
The four apps
apps/web is the product: a Next.js 16 App Router app with Cache Components enabled in next.config.ts. It depends on database for generated types and on @nextbase/typescript-config for the shared tsconfig. Its next.config.ts derives the Supabase Storage image pattern from NEXT_PUBLIC_SUPABASE_URL.
apps/admin is the administration app (package name admin, next dev --port 3002). It serves /app-admin, the feedback and roadmap moderation views, the Admin MCP and the support sign-in flow, and it deploys independently from the web app against the same Supabase project. Root scripts pnpm admin#dev and pnpm admin#build target it. docs/admin-app.md describes the split.
apps/database owns Supabase. supabase/config.toml configures the local project with the stack engine enabled, supabase/migrations/ holds the SQL migrations, supabase/seed.sql seeds test users and content, and supabase/tests/ holds pgTap suites. lib/database.types.ts is generated and exported as database/types. The package pins the supabase CLI to 2.121.0.
apps/email is the React Email preview server (pnpm --filter @nextbase/email dev, port 3001). The templates in emails/ are the design source; the web app renders its own copies with @react-email/* components.
Where to make changes
Routes and pages
Web routes are under apps/web/src/app, grouped by access level:
(external-pages): landing page,/blog,/changelog,/terms,/docsentry.(dynamic-pages)/(login-pages):/login,/sign-up.(dynamic-pages)/(misc-login-pages):/forgot-password,/update-password,/confirm-delete-user,/auth/callback,/auth/confirm.(dynamic-pages)/(updates-pages): public/feedbackand/roadmap.(dynamic-pages)/(authenticated-pages):/dashboard,/onboarding,/user/*,(application-pages)/workspace/[workspaceSlug]/*(home, projects, settings with billing and members) and(application-pages)/project/[projectSlug].api/:stripe/webhooks,invitations,chat,search,revalidate,internal/admin-revalidate(called by the admin app),token/refresh,health,v1/meand the OG image routes.mcp/[...path],oauth/[...path]and.well-known/*: the User MCP route handlers.
Admin routes are under apps/admin/src/app: app-admin/(admin-pages)/* (dashboard, users, workspaces, marketing/{blog,changelog,authors,tags}, payment-gateway/{customers,invoices,subscriptions}, settings), (dynamic-pages)/(updates-pages) for feedback and roadmap moderation, api/admin/marketing/media, login, logout, auth/{callback,confirm} and the Admin MCP handlers under mcp, oauth and .well-known.
When you add a protected web route, add its matcher to apps/web/src/middlewares/paths.ts, otherwise proxy.ts will not run the auth chain for it. The admin app's proxy.ts uses a fixed matcher for /app-admin, /feedback and /roadmap.
Server and data access
Web database access is in apps/web/src/data/, split into user/ (RLS-scoped, user session), anon/ (public marketing data) and auth/ (sign-in actions). Admin queries that need the service-role client live in apps/admin/src/data/admin/, gated by adminActionClient from apps/admin/src/lib/safe-action.ts. Files there start with "use server". Server actions are built with next-cool-action wrappers (apps/web/src/lib/safe-action.ts and the admin equivalent).
Pick the Supabase client that matches the context from src/supabase-clients/ in each app: server component, server action, route handler, middleware, browser, admin or (web only) Unkey.
Schema and migrations
Add a timestamped SQL file to apps/database/supabase/migrations/. Enable RLS on every new table and write its policies in the same file. Then:
pnpm --filter database exec supabase db reset --local
pnpm gen-types-local
pnpm --filter database test-dbAdd a pgTap test under apps/database/supabase/tests/ for the new policies. Use pnpm --filter database exec supabase migration list --local to check what is applied.
Authentication
Server actions in apps/web/src/data/auth/auth.ts, UI in apps/web/src/components/authentication/, middleware in apps/web/src/middlewares/, claims schema in apps/web/src/utils/zod-schemas/user-claims-schema.ts, admin check in utils/is-supabase-user-app-admin.ts (both apps). Admin login and gate in apps/admin/src/app/login/ and apps/admin/src/proxy.ts. Local Auth settings are in apps/database/supabase/config.toml. See authentication.
Emails
Templates in apps/email/emails/. Sending goes through src/utils/api-routes/utils.ts in each app, which chooses Mailpit (USE_LOCAL_EMAIL=true) or Resend. The web copy of send-email-mailpit.ts reads MAILPIT_URL. Invitation and notification emails are triggered from apps/web/src/data/user/invitation.tsx and apps/web/src/data/user/user.tsx.
Billing
apps/web/src/payments/abstract-payment-gateway.ts defines the interface; stripe-payment-gateway.ts implements it (the admin app has its own copy under apps/admin/src/payments/). The webhook route is apps/web/src/app/api/stripe/webhooks/route.ts. Self-service billing UI is under workspace/[workspaceSlug]/settings/billing in the web app; the payment gateway admin pages are under apps/admin/src/app/app-admin/(admin-pages)/payment-gateway.
Content publishing across apps
Blog, changelog and feedback are edited in the admin app and read by the web app. After a publish, apps/admin/src/lib/revalidate-web-app.ts posts to the web app's /api/internal/admin-revalidate with the shared REVALIDATE_SECRET so the web app's cached pages refresh.
Background work
There is no job queue or worker in this kit. Stripe webhooks, Supabase Realtime channels and the on-demand POST /api/revalidate endpoint are the asynchronous entry points.
MCP
src/mcp/ in each app holds the protocol core, the Supabase adapter and the record store; the route files under app/mcp, app/oauth and app/.well-known are one-liners. The web copy serves the user audience, the admin copy the admin audience. See agent connections.
Tests
- Unit tests sit next to the code as
*.test.tsand run withpnpm test(Vitest with--root srcin bothwebandadmin). - Browser tests are in
apps/web/e2e/(anon,user,admin,invitations,password-updation, with_setupsand_helpers) and run withpnpm test-integrationusingapps/web/playwright/playwright.config.mts. The config starts both the web and admin production servers; theadminspecs run against the admin origin. - Database tests are in
apps/database/supabase/tests/and run withpnpm --filter database test-db. pnpm verify:localruns lint, typecheck, unit tests and a compile-mode build of the web app;pnpm signoffruns it and posts asignoffstatus with thegh-signoffextension.
The nightly integration-tests.yml workflow starts Supabase, resets the database, runs pgTap, builds and runs Playwright. release.yml opens a Changesets version PR on pushes to main.