Nextbase Docs
Nextbase Docs
HomeBlogWelcome to Nextbase v5!Choose your starterGetting startedLocal Supabase per agent
Next.js + Cloudflare D1InstallationEnvironment variablesAuthenticationAgent connectionsProject structureDeployment
AuthenticationWorkspaces and the admin area
Building with agentsEmails with Resend
TroubleshootingNextbase v5
The 8 starter kitsNext.js + Cloudflare D1

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.

The kit deploys as three Cloudflare Workers: the OpenNext web Worker, the OpenNext admin Worker and the platform Worker. All three need the same application D1 database, and the two Next.js Workers also need the OpenNext cache resources. The shared Cloudflare deployment guide has the platform-generic steps; this page lists what is specific to this kit.

Plan

The live demo runs on Workers Paid. During testing, the Better Auth password work exceeded the Free plan's CPU limit and produced intermittent errors. Check your plan if login fails only sometimes after deploying. See troubleshooting.

1. Create the Cloudflare resources

Create these in your account, using the names you want (the checked-in names are the local defaults):

ResourceDefault nameUsed by
D1 database (application)nextbase-appall three Workers, binding APP_DB
D1 database (OpenNext tag cache)nextbase-next-cacheweb and admin, binding NEXT_TAG_CACHE_D1
R2 bucket (media)nextbase-mediaall three Workers, binding MEDIA_BUCKET
R2 bucket (OpenNext incremental cache)nextbase-next-cacheweb and admin, binding NEXT_INC_CACHE_R2_BUCKET
Queuenextbase-jobsplatform producer and consumer
Queue (dead letter)nextbase-jobs-dlqplatform consumer
Queue (terminal)nextbase-jobs-dlq-persistence-failuresno consumer; see the README runbook

The queue commands from the repository are:

pnpm --filter @nextbase/platform-worker exec wrangler queues create nextbase-jobs --message-retention-period-secs 86400
pnpm --filter @nextbase/platform-worker exec wrangler queues create nextbase-jobs-dlq --message-retention-period-secs 86400
pnpm --filter @nextbase/platform-worker exec wrangler queues create nextbase-jobs-dlq-persistence-failures --message-retention-period-secs 86400

Durable Objects (DOQueueHandler, RealtimeChannel) and the two rate limiters are created from the wrangler.jsonc declarations on first deploy. The repository doesn't include commands for creating the D1 databases or R2 buckets; use wrangler d1 create and wrangler r2 bucket create, or the dashboard.

2. Edit the production environment in the three Wrangler files

The top-level D1 IDs (00000000-...) are placeholders for local work. Each wrangler.jsonc (apps/web, apps/admin, apps/platform-worker) also has an env.production block that repeats every binding with the demo's real resource IDs, names and account_id. Replace those values with your own; keep the binding names unchanged. The two Next.js production blocks also:

  • give each Worker a distinct name (nextbase-cloudflare-web-production, nextbase-cloudflare-admin-production) and point WORKER_SELF_REFERENCE at that name,
  • set BETTER_AUTH_URL and NEXT_PUBLIC_SITE_URL to that Worker's public origin (the web origin on web, the admin origin on admin),
  • cross-reference the other app: NEXT_PUBLIC_ADMIN_APP_URL on web, NEXT_PUBLIC_WEB_APP_URL and ADMIN_APP: "true" on admin,
  • set ADMIN_EMAIL to the address that should become the first admin,
  • set USE_LOCAL_EMAIL to "false" and MAILPIT_API_URL to "".

The platform Worker's production block mirrors its bindings and vars, and adds MAIL_FROM with a verified sender such as Your App <no-reply@your-domain>.

Run pnpm cf:typegen:check afterwards so the generated binding types still match.

3. Set the Worker secrets

From each app directory, add the secrets for the production environment:

cd apps/web
pnpm exec wrangler secret put BETTER_AUTH_SECRET --env production
pnpm exec wrangler secret put REALTIME_TOKEN_SECRET --env production
pnpm exec wrangler secret put REVALIDATE_SECRET --env production
pnpm exec wrangler secret put STRIPE_SECRET_KEY --env production
pnpm exec wrangler secret put STRIPE_WEBHOOK_SECRET --env production
# optional: RESEND_API_KEY, OPENAI_API_KEY, GOOGLE_*, GITHUB_*, TWITTER_*

cd ../admin
pnpm exec wrangler secret put BETTER_AUTH_SECRET --env production   # a different value from web
pnpm exec wrangler secret put REALTIME_TOKEN_SECRET --env production
pnpm exec wrangler secret put REVALIDATE_SECRET --env production
pnpm exec wrangler secret put STRIPE_SECRET_KEY --env production
pnpm exec wrangler secret put STRIPE_WEBHOOK_SECRET --env production
# optional: RESEND_API_KEY, OPENAI_API_KEY, GOOGLE_*, GITHUB_*, TWITTER_*

cd ../platform-worker
pnpm exec wrangler secret put REALTIME_TOKEN_SECRET --env production
pnpm exec wrangler secret put INTERNAL_SERVICE_SECRET --env production
pnpm exec wrangler secret put RESEND_API_KEY --env production
pnpm exec wrangler secret put STRIPE_SECRET_KEY --env production

REALTIME_TOKEN_SECRET must be identical on all three Workers and at least 32 characters. INTERNAL_SERVICE_SECRET must be a separate value of at least 32 characters. The admin Worker should get its own BETTER_AUTH_SECRET; the admin uses a different cookie prefix, so nothing needs to be shared. The full list is in environment variables.

4. Apply the migrations

Both D1 databases need their migrations before the first request:

pnpm db:migrate:production

This runs wrangler d1 migrations apply --remote --env production for APP_DB and NEXT_TAG_CACHE_D1 using apps/web/wrangler.jsonc. The admin Worker shares those databases and owns no migrations. pnpm db:migrate:remote runs the same two commands without --env production, so it only works if your top-level bindings already point at the production databases. The application migrations include 0010_famous_groot.sql, which creates mcp_records; both Next.js Workers need it before serving OAuth connections.

5. Build and deploy

Deploy the platform Worker first so the PLATFORM service binding exists when the Next.js Workers go live. The root script does that in order:

NEXT_PUBLIC_SITE_URL=https://your-domain pnpm cf:deploy:production

cf:deploy:production runs wrangler deploy --env production in apps/platform-worker, then cf:deploy:production in apps/web, which builds with opennextjs-cloudflare build --env production and deploys. NEXT_PUBLIC_SITE_URL has to be present in the shell because it is compiled into the browser bundle; the web build script stops with Set NEXT_PUBLIC_SITE_URL to the public origin of this deployment when it is missing, and no default is baked in. A build made with the default environment contains the placeholder D1 IDs and the localhost URL, so always rebuild with --env production.

Then deploy the admin Worker:

pnpm cf:deploy:admin:production

The admin build script in apps/admin/package.json sets NEXT_PUBLIC_SITE_URL and NEXT_PUBLIC_WEB_APP_URL inline, and the checked-in values are the demo's https://nextjs-d1-admin.usenextbase.com and https://nextjs-d1.usenextbase.com. Edit that cf:build:production script to your own admin and web origins before running it.

pnpm cf:deploy and pnpm cf:deploy:admin are the same flows without --env production; use them only when your top-level configuration is the production one. pnpm cf:build builds all three Workers without deploying.

GitHub Actions. .github/workflows/deploy.yml is a manual workflow_dispatch workflow with an app input (web or admin). It installs, runs typecheck, tests and lint, then runs pnpm --filter <app> cf:deploy:production with CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID from repository secrets and NEXT_PUBLIC_SITE_URL set in the workflow file to the demo URL. Change that value to your web origin. It does not deploy the platform Worker; run that from your machine or add a step.

6. Configure the external services

  • Auth callbacks: register https://your-domain/api/auth/callback/google, /github and /twitter for the providers you enabled, and the same paths on the admin origin if administrators should use a provider there. Each Worker trusts only its own NEXT_PUBLIC_SITE_URL, so the public origins and the Worker vars must match exactly.
  • Stripe: point the webhook endpoint at https://your-domain/api/stripe/webhooks on the web Worker and store its signing secret in STRIPE_WEBHOOK_SECRET. The admin Worker reads billing records from D1 and queues product synchronization to the platform Worker, which keeps the Stripe secret.
  • Email: set RESEND_API_KEY on the platform Worker and MAIL_FROM to a sender on a verified Resend domain. Auth emails are queued in the D1 outbox and dispatched by the platform Worker's cron every minute. See emails.
  • Custom domains: the demo serves https://nextjs-d1.usenextbase.com and https://nextjs-d1-admin.usenextbase.com through two small Cloudflare Pages projects, infrastructure/demo-domain and infra/admin-domain, that forward to the production Workers with an APP service binding. Deploy the web proxy with the command in infrastructure/demo-domain/README.md and the admin proxy with pnpm --filter admin cf:deploy:domain, then point each DNS CNAME at the Pages hostname (nextjs-d1-starter-domain.pages.dev and nextjs-d1-admin-domain.pages.dev for the demo). Workers custom domains on the Workers themselves work as well; whichever you pick, keep each Worker's BETTER_AUTH_URL and NEXT_PUBLIC_SITE_URL equal to its public address.
  • Agent connections: no extra variable. The User MCP is https://your-domain/mcp/user on the web Worker and the Admin MCP is https://your-admin-domain/mcp/admin on the admin Worker, each derived from that Worker's NEXT_PUBLIC_SITE_URL.

7. Smoke checks

  1. Open the web site, sign up with the ADMIN_EMAIL address and open the verification email. Then open the admin origin, sign in with that account and confirm you land in /app-admin.
  2. Sign up with a second address on the web site, verify, create a workspace and a project.
  3. Request https://your-domain/mcp/user and https://your-admin-domain/mcp/admin without a token and confirm a 401 with a WWW-Authenticate header, then check /.well-known/oauth-authorization-server/oauth returns JSON on both origins.
  4. Connect an agent to /mcp/user on the web origin, approve one workspace and list projects. Try /mcp/admin on the admin origin with the admin account.
  5. From /app-admin/users, impersonate the second account and confirm you are redirected to the web origin with the impersonation banner, and that the banner's link returns you to the admin app.
  6. Check https://<platform-worker>.workers.dev/health and watch the Worker logs for outbox or CPU-limit errors during the first few sign-ups.
  7. If you use Stripe, trigger a test checkout and confirm the webhook projection lands in /app-admin/payment-gateway on the admin origin.

If something fails, troubleshooting covers the common D1, login and MCP cases.

Project structure

The five apps in the Next.js + Cloudflare D1 monorepo, the key config files, and where to make each kind of change.

TanStack + Cloudflare D1

What the TanStack + Cloudflare D1 starter is, what it ships with, and what you need before you start.

On this page

1. Create the Cloudflare resources2. Edit the production environment in the three Wrangler files3. Set the Worker secrets4. Apply the migrations5. Build and deploy6. Configure the external services7. Smoke checks