Project structure
The five apps in the Next.js + Cloudflare D1 monorepo, the key config files, and where to make each kind of change.
The repository is a pnpm workspace driven by Turborepo. Five apps share one Drizzle schema package; there is no separate packages/ library besides the TypeScript config.
The five apps
| App | Package name | Role |
|---|---|---|
apps/web | web | Next.js 16 customer application compiled to a Cloudflare Worker by OpenNext. Owns public pages, user and workspace routes, server actions, auth, payments, the realtime client, the User MCP and the impersonation callback. |
apps/admin | admin | Next.js 16 administrator application compiled to its own Worker. Owns /app-admin, feedback and roadmap moderation, the admin login and the Admin MCP. Reuses web modules through its @/* path alias and declares web as a workspace dependency. |
apps/database | database | Drizzle D1 schema, migrations, repositories, tenant authorization, the compatibility query facade and the transactional outbox. Imported by all three Workers. |
apps/platform-worker | @nextbase/platform-worker | Queue consumer, scheduled outbox dispatcher (cron */1 * * * *), Stripe projection and product synchronization, deferred R2 deletion, email delivery and the RealtimeChannel Durable Object. |
apps/email | @nextbase/email | React Email templates with a local preview server on port 3001. |
Root-level helpers: scripts/ holds the shell and TypeScript scripts behind the root pnpm commands (seed, reset, verify, preview, typegen), patches/ holds the pnpm patch for @opennextjs/aws, docker-compose.yml defines only Mailpit, and docs/mcp-connections.md documents the MCP servers. infrastructure/demo-domain and infra/admin-domain are two tiny Cloudflare Pages projects that forward the demo's public hostnames to the production web and admin Workers through a service binding. .github/workflows/ has the CI suite, the nightly Playwright run (e2e.yml), the changesets release flow and a manual deploy.yml that deploys either the web or the admin Worker.
Wrangler configuration
Each Worker has its own wrangler.jsonc. The web and admin Workers each declare two D1 databases, two R2 buckets, two service bindings, a Durable Object and two rate limiters; the admin file adds ADMIN_APP and NEXT_PUBLIC_WEB_APP_URL to its vars, and the web file adds NEXT_PUBLIC_ADMIN_APP_URL. Keep the application bindings and the OpenNext cache bindings apart:
| Purpose | Binding | Local resource name |
|---|---|---|
| Application database | APP_DB | nextbase-app, migrations in apps/database/migrations |
| OpenNext tag cache | NEXT_TAG_CACHE_D1 | nextbase-next-cache, migrations in apps/web/cache-migrations |
| Uploads and public media | MEDIA_BUCKET | nextbase-media |
| OpenNext incremental cache | NEXT_INC_CACHE_R2_BUCKET | nextbase-next-cache |
| OpenNext cache queue | NEXT_CACHE_DO_QUEUE | DOQueueHandler Durable Object |
| Platform Worker | PLATFORM | service binding to nextbase-platform-worker |
| Self reference | WORKER_SELF_REFERENCE | nextbase-cloudflare-web or nextbase-cloudflare-admin |
Each app's open-next.config.ts wires the R2 incremental cache, D1 tag cache and Durable Object queue. The platform Worker shares APP_DB and MEDIA_BUCKET, and adds the JOBS_QUEUE producer, two queue consumers (nextbase-jobs and nextbase-jobs-dlq) and the REALTIME Durable Object. The generated worker-configuration.d.ts in each app is the type source for env; regenerate all of them with pnpm cf:typegen. All three files carry an env.production block with the demo's remote resources; see deployment.
Where to make changes
Customer routes and pages. apps/web/src/app/. Authenticated pages live under (dynamic-pages)/(authenticated-pages)/: (application-pages)/workspace/[workspaceSlug], (application-pages)/project/[projectSlug], dashboard, onboarding and user/.... Login and recovery pages are under (login-pages) and (misc-login-pages), including the auth/impersonate route that redeems admin impersonation tokens. Public marketing, blog, changelog and docs pages are under (external-pages) and (fumadocs). API routes are in app/api/, including api/auth/[...all], api/stripe/webhooks, api/revalidate, api/realtime and api/media.
Admin routes and pages. apps/admin/src/app/. The admin panel is app-admin/(admin-pages)/ with users, workspaces, settings, payment-gateway and marketing/{authors,blog,changelog,tags}, plus @sidebar and @navbar parallel routes. Feedback and roadmap moderation are under (updates-pages)/. The admin login flow is login/, forgot-password/, update-password/, two-factor/ and auth/. Admin-only server code sits in apps/admin/src/data/admin/; anything not found under apps/admin/src resolves to apps/web/src through the @/* alias, so shared components and utilities stay in apps/web.
Server data access. apps/web/src/data/ holds server-only functions grouped as anon, auth and user; the admin app keeps its own under apps/admin/src/data/admin/. They call the D1 clients in apps/web/src/d1-clients/ and the repositories exported by the database package (database/operations, database/authorization, database/data-client). Bindings come from getCloudflareContext through apps/web/src/cloudflare/context.ts; never create a global database client.
Schema and migrations. Edit the Drizzle schema in apps/database/src/schema/ (core.ts, domain.ts, projects.ts, users.ts, auth.ts), then run pnpm db:generate to write a migration to apps/database/migrations, pnpm db:check to validate the journal, pnpm gen-types to refresh apps/database/lib/database.types.ts, and pnpm db:migrate to apply locally. Better Auth tables are generated separately with pnpm auth:generate into auth.generated.ts. pnpm verify:local fails if the schema snapshot or generated types drift. The admin app owns no migrations.
Authorization. apps/database/src/authorization.ts defines workspace roles, permissions, legacy permission keys and the Actor type. Add new permissions there and check them in the repository, not in the component.
Authentication. apps/web/src/auth/server.ts and the files beside it, shared by both Next.js apps; see authentication.
Emails. Templates in apps/email/emails/. Sending goes through enqueueOutboxItem from database/outbox with the email topic; the platform Worker delivers through Mailpit locally or Resend in production. Auth emails are composed inline in apps/web/src/auth/server.ts.
Background jobs and realtime. apps/platform-worker/src/index.ts contains the queue handler, the scheduled dispatcher, the operator recovery routes and the Durable Object. Realtime token signing lives in apps/web/src/realtime/. The dead-letter recovery runbook is in the repository README.md.
Payments. apps/web/src/payments/ holds the abstract gateway, the Stripe implementation and the billing catalog. Webhook projection is done asynchronously by the platform Worker. The admin payment pages read the projected D1 billing records and queue product synchronization to the platform Worker.
MCP. apps/web/src/mcp/, with route files in both apps/web/src/app and apps/admin/src/app; see agent connections.
Tests. Unit tests sit next to the code as *.test.ts and run with pnpm test (Vitest in apps/web and apps/admin, Vitest with the Workers pool in apps/platform-worker, node --test through tsx in apps/database). Playwright specs are in apps/web/e2e/ (anon, user, admin, invitations and password updates) with the config in apps/web/playwright/; run them with pnpm test-integration, which builds the admin and web OpenNext Workers first. CI runs the Playwright suite nightly from .github/workflows/e2e.yml.
Commands you will use most
pnpm dev # every workspace dev script in parallel
pnpm web#dev # web app only
pnpm admin:dev # admin app only, port 3001
pnpm worker:dev # platform worker only
pnpm db:migrate # apply local D1 migrations (app + cache)
pnpm db:seed # idempotent local demo data
pnpm db:reset # wipe local D1 state and re-migrate
pnpm cf:typegen # regenerate worker-configuration.d.ts in all three Workers
pnpm verify:local # full local gate
pnpm cf:build # build platform, web and admin Workers
pnpm cf:preview # build everything and run the platform and web Workers in workerdAgent connections
The User and Admin MCP servers in the Next.js + Cloudflare D1 starter: endpoints, tools, OAuth behaviour and where the code lives.
Deployment
Provision Cloudflare resources, configure production bindings and secrets, migrate D1 and deploy the web, admin and platform Workers of the Next.js + Cloudflare D1 starter.