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

Deployment

Provision Cloudflare resources, align the web, admin and jobs Wrangler configs, apply D1 migrations and deploy the three Workers.

This kit deploys as three Cloudflare Workers that share one D1 database: the web Worker (apps/web), the admin Worker (apps/admin) and the jobs Worker (apps/jobs). The repository ships preview and production environments in all three Wrangler files. The platform-generic steps are in deploy on Cloudflare; this page covers what is specific to this repository.

The repository's docs/operations.md is the runbook for migrations, outbox recovery, webhooks, rollback and secret rotation, and the "Separate administrator app" section of README.md covers the admin Worker. Read both before your first production release.

Create the Cloudflare resources

Create separate resources for preview and production. Never point preview bindings at production resources.

pnpm exec wrangler d1 create nextbase-prod
pnpm exec wrangler r2 bucket create nextbase-public-media
pnpm exec wrangler r2 bucket create nextbase-private-media
pnpm exec wrangler queues create nextbase-jobs
pnpm exec wrangler queues create nextbase-jobs-dlq

Repeat with the resource names declared in the env.preview and env.production blocks: nextbase-preview-* and nextbase-production-* for the R2 buckets, queues and dead-letter queues. Pick your own D1 database names; the shipped values are placeholders. The admin Worker creates nothing new: it binds the same database, buckets and queue.

Durable Objects need no provisioning. Wrangler creates the RealtimeRoom and AuthRateLimiter namespaces from the migrations block in apps/web/wrangler.jsonc on first deploy. Keep both migration tags (v1, v2) when you rename Workers or add environments.

Update the three Wrangler files and keep them aligned

Edit apps/web/wrangler.jsonc, apps/admin/wrangler.jsonc and apps/jobs/wrangler.jsonc together. For each environment:

SettingWeb WorkerAdmin WorkerJobs WorkerMust match
namenextbase-tanstack-d1-preview / -productionnextbase-tanstack-d1-admin-preview / -productionnextbase-jobs-preview / -productionEvery durable_objects.bindings[].script_name in the admin and jobs files must equal the web Worker's name for the same environment.
d1_databases[].bindingAPP_DBAPP_DBAPP_DBYes, and keep the name APP_DB.
d1_databases[].database_name and database_idyour databaseyour databaseyour databaseYes. Replace the 11111111-... placeholder and the demo's production ID with the real ID from wrangler d1 create.
d1_databases[].migrations_dir../database/migrationssamesameYes.
r2_bucketsPUBLIC_MEDIA, PRIVATE_MEDIAsame bindingssame bindingsSame bucket names.
queues.producers[].queuethe environment's queuesame queuesame queueYes. Only the jobs file lists the consumers and the -dlq queue.
vars.SITE_URLyour public web originyour admin originthe web originWeb and jobs identical; admin is its own origin.
vars.ADMIN_APP_URLthe admin originMust equal the admin SITE_URL.
vars.WEB_APP_URLthe web originMust equal the web SITE_URL. Never set it to the admin origin.
vars.ADMIN_APPtrueKeep it true on the admin Worker only.
vars.ADMIN_EMAILa real mailboxsamesameYes.
vars.API_CORS_ORIGINS, vars.UNKEY_API_ID, vars.APP_NAMEwebadminSet on web; the admin file carries the same keys.
vars.MAIL_FROMoptional verified senderJobs only.

Only the web Worker defines the RealtimeRoom and AuthRateLimiter classes. The admin Worker reaches both and the jobs Worker reaches REALTIME through script_name, so a mismatch there breaks realtime invalidations or admin rate limiting even though the deploy succeeds. Deploy the web Worker before the other two so the referenced script exists.

All three Workers read the same database

If the APP_DB entries point at different databases, the web app will enqueue emails and jobs that the jobs Worker never sees, verification emails will never be delivered, and the admin console will show a different set of users. Check the database_id in all three files for the same environment before deploying.

Set the secrets on the Worker that uses them

pnpm --filter @nextbase/web exec wrangler secret put BETTER_AUTH_SECRET --env preview
pnpm --filter @nextbase/web exec wrangler secret put ANALYTICS_PSEUDONYM_SECRET --env preview
pnpm --filter @nextbase/web exec wrangler secret put STRIPE_SECRET_KEY --env preview
pnpm --filter @nextbase/web exec wrangler secret put STRIPE_WEBHOOK_SECRET --env preview
pnpm --filter @nextbase/web exec wrangler secret put OPENAI_API_KEY --env preview
pnpm --filter @nextbase/web exec wrangler secret put UNKEY_ROOT_KEY --env preview
pnpm --filter @nextbase/admin exec wrangler secret put BETTER_AUTH_SECRET --env preview
pnpm --filter @nextbase/jobs exec wrangler secret put STRIPE_SECRET_KEY --env preview
pnpm --filter @nextbase/jobs exec wrangler secret put RESEND_API_KEY --env preview
pnpm --filter @nextbase/jobs exec wrangler secret put UNKEY_ROOT_KEY --env preview
pnpm --filter @nextbase/jobs exec wrangler secret put CLOUDFLARE_API_TOKEN --env preview
pnpm --filter @nextbase/jobs exec wrangler secret put CLOUDFLARE_ZONE_ID --env preview

Repeat with --env production. BETTER_AUTH_SECRET is mandatory on the web and admin Workers: both throw at startup on a non-localhost SITE_URL without it, and the admin value must be a different secret from the web value. Add the social provider client IDs and secrets the same way if you use them. RESEND_API_KEY and the cache-purge credentials belong only on the jobs Worker. The full list is on the environment variables page.

Apply the D1 migrations

Apply the schema before the first application deploy, and again whenever a release adds a migration:

pnpm db:migrate:preview
pnpm db:migrate:production

Both scripts run wrangler d1 migrations apply APP_DB --remote with the matching --env against apps/web/wrangler.jsonc. pnpm db:migrate:remote is an alias for the preview command. The run includes 0025_user_admin_mcp.sql (the mcp_records table), which both the web and admin Workers need before they serve OAuth connections.

Before a destructive migration, record a D1 Time Travel bookmark and the release SHA. Never reverse an applied migration; ship a forward repair migration instead.

Build and deploy the Workers

pnpm cf:deploy:preview
pnpm cf:deploy:admin:preview
pnpm cf:deploy:production
pnpm cf:deploy:admin:production

cf:deploy:<env> sets CLOUDFLARE_ENV to the environment, runs the React Router build for @nextbase/web, deploys the web Worker with wrangler deploy --env, then deploys the jobs Worker with wrangler deploy --env. cf:deploy:admin:<env> does the same for @nextbase/admin alone. Run the web script first so the Durable Object classes exist for the admin and jobs bindings. The CLOUDFLARE_ENV value matters: the Vite build generates the redirected Wrangler configuration from it. Running wrangler deploy --env production after a build without that variable can keep the local placeholder database ID, so always use the root scripts.

The repository also has a manual GitHub Actions workflow, .github/workflows/deploy.yml. Trigger it with workflow_dispatch, choose the environment (preview or production) and the app (web or admin); it runs pnpm verify and pnpm test:integration, applies db:migrate:<env> only for the web app, then runs the matching cf:deploy:* script. It needs CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID as secrets on the GitHub deployment environment of the same name.

Point auth, email and MCP at the public origins

  • SITE_URL in the web and jobs Wrangler files must be the address users visit, and SITE_URL in the admin file must be the admin address. Better Auth uses each as baseURL and trusted origin, so cookies, verification links and OAuth callbacks break if they differ.
  • Set web ADMIN_APP_URL and admin WEB_APP_URL to each other's origin. Impersonation hands the browser from the admin origin to WEB_APP_URL + /auth/impersonate with a one-minute, single-use token and returns to ADMIN_APP_URL + /app-admin/users.
  • Social providers: register SITE_URL + /api/auth/callback/<provider> with each provider you enabled, once per origin that should offer the button.
  • Email sender: the jobs handler sends from MAIL_FROM when set, otherwise from ADMIN_EMAIL, through Resend. Verify that domain in Resend. See emails with Resend.
  • Stripe: point the webhook endpoint at the web SITE_URL + /api/stripe/webhooks and subscribe to the event types listed in docs/operations.md.
  • MCP: the User MCP is the web SITE_URL + /mcp/user and the Admin MCP is the admin SITE_URL + /mcp/admin. There is no separate MCP_APP_URL in this kit; each Worker's origin comes from its own SITE_URL.
  • Public API: set API_CORS_ORIGINS to the exact origins allowed to call /api/v1/me.
  • Custom domains: the demo attaches tanstack-d1.usenextbase.com and tanstack-d1-admin.usenextbase.com through two small Cloudflare Pages proxies (infrastructure/demo-domain and infra/admin-domain) with a service binding to the Worker. Deploy the admin proxy with pnpm --filter @nextbase/admin cf:deploy:domain and point the admin DNS CNAME at its pages.dev host, or attach a custom domain directly to each Worker instead.

Smoke-check the live app

  1. Open /sign-up on the web origin, create an account, and confirm the verification email arrives through Resend.
  2. Sign in, complete /onboarding, create a workspace and a project.
  3. Grant your account the admin role in the production database (see authentication), open /login on the admin origin and confirm you land on /app-admin.
  4. Open /mcp/connections/user on the web origin and /mcp/connections/admin on the admin origin, confirm the displayed addresses use the right origins, then connect an agent and call projects_list.
  5. From the admin users section, start an impersonation and confirm you land on the web app read-only and that Stop impersonating returns you to the admin origin.
  6. Request the jobs Worker's /health endpoint and check that pending and dead counts stay low.
  7. Watch Workers Logs for jobs.retrying, jobs.dead and jobs.dead_lettered events after the first minute cron.
  8. Send a signed Stripe test webhook and upload a public and a private media file if you use those features.

Rollback

Roll application code back by deploying the previous SHA with the same cf:deploy:* script, web first, then admin and jobs. Do not reverse an applied migration; use a forward repair migration or D1 Time Travel during an incident. The web Worker returns an X-Request-Id header on every response; start any investigation from that ID in operational_exceptions, as described in docs/operations.md.

If the Worker deploys but login or the database fails, see troubleshooting.

Project structure

The monorepo layout of the TanStack + Cloudflare D1 starter and where to make common changes.

Authentication

The same login provider for your users, your app and agent connections.

On this page

Create the Cloudflare resourcesUpdate the three Wrangler files and keep them alignedSet the secrets on the Worker that uses themApply the D1 migrationsBuild and deploy the WorkersPoint auth, email and MCP at the public originsSmoke-check the live appRollback