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

Environment variables

Every variable in the Next.js + Cloudflare D1 starter's example files, which Worker reads it, and where the value comes from.

This kit splits configuration in three places:

  • apps/web/.dev.vars, apps/admin/.dev.vars and apps/platform-worker/.dev.vars hold Worker secrets for local development. In production the same names become Wrangler secrets. All three files are gitignored.
  • The vars block in each wrangler.jsonc holds non-secret defaults that are safe to commit.
  • apps/web/.env.local holds values the Next.js toolchain reads outside the Worker, such as NEXT_PUBLIC_* build variables and the Sentry upload settings. It repeats some of the Worker values so plain next dev tooling has them too. setup.sh creates it from the example when it is missing.

Never put a real key in a vars block or in the example files. Run pnpm cf:typegen after adding or renaming a secret in a .dev.vars.example file; CI checks the generated worker-configuration.d.ts with pnpm cf:typegen:check.

apps/web/.dev.vars

Secrets for the web Worker. Copy from apps/web/.dev.vars.example.

VariableRequiredPurposeWhere to get it
BETTER_AUTH_SECRETRequiredSigns Better Auth sessions and tokensGenerate a random string of at least 32 characters
STRIPE_SECRET_KEYRequired for billingCreates checkout and portal sessionsStripe dashboard, API keys
STRIPE_WEBHOOK_SECRETRequired for billingVerifies incoming webhook signaturesstripe listen locally, or the webhook endpoint in Stripe
REALTIME_TOKEN_SECRETRequiredSigns channel-scoped realtime tokens; must match the admin and platform Workers exactlyGenerate at least 32 random characters
RESEND_API_KEYOptionalSends email when USE_LOCAL_EMAIL is not trueResend dashboard
OPENAI_API_KEYOptionalImage generation and AI helpersOpenAI dashboard
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETOptionalEnables Google sign-in when both are setGoogle Cloud OAuth client
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETOptionalEnables GitHub sign-in when both are setGitHub OAuth app
TWITTER_API_KEY, TWITTER_API_SECRETOptionalEnables X sign-in when both are setX developer portal
REVALIDATE_SECRETRequiredProtects the /api/revalidate routeGenerate a random string

All of these are server-only. Values shorter than 32 characters for REALTIME_TOKEN_SECRET are rejected at runtime.

apps/admin/.dev.vars

Secrets and local overrides for the admin Worker. Copy from apps/admin/.dev.vars.example. The first block repeats the web Worker's secret names; the admin app runs the same Better Auth, Stripe, email and provider code against the same D1 database.

VariableRequiredPurposeWhere to get it
BETTER_AUTH_SECRETRequiredSigns the admin app's own sessions; use a value independent from the web WorkerGenerate a random string of at least 32 characters
STRIPE_SECRET_KEYRequired for billingReads billing data for the payment gateway pagesSame key as the web Worker
STRIPE_WEBHOOK_SECRETRequired for billingPresent for the shared Stripe moduleSame value as the web Worker
REALTIME_TOKEN_SECRETRequiredSame value as the web and platform WorkersCopy from apps/web/.dev.vars
RESEND_API_KEYOptionalSends email when USE_LOCAL_EMAIL is not trueResend dashboard
OPENAI_API_KEYOptionalAI helpers in the admin content toolsOpenAI dashboard
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETOptionalGoogle sign-in on the admin login pageGoogle Cloud OAuth client
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETOptionalGitHub sign-in on the admin login pageGitHub OAuth app
TWITTER_API_KEY, TWITTER_API_SECRETOptionalX sign-in on the admin login pageX developer portal
REVALIDATE_SECRETRequiredShared revalidation secretSame value as the web Worker
ADMIN_APPRequiredtrue marks this Worker as the admin app: admin cookie prefix, admin MCP audience, admin-only data pathsKeep true
NEXT_PUBLIC_SITE_URLRequiredThe admin app's own originhttp://localhost:3001 locally
BETTER_AUTH_URLRequiredBetter Auth base URL for the admin apphttp://localhost:3001 locally
NEXT_PUBLIC_WEB_APP_URLRequiredThe customer web app's origin, used for the impersonation callback and media URLshttp://localhost:3000 locally

The last four are also set in the vars block of apps/admin/wrangler.jsonc; the .dev.vars copies exist so next dev has them before the Wrangler bindings load.

apps/platform-worker/.dev.vars

Secrets for the background Worker. Copy from apps/platform-worker/.dev.vars.example.

VariableRequiredPurposeWhere to get it
REALTIME_TOKEN_SECRETRequiredSame value as the web and admin WorkersCopy from apps/web/.dev.vars
INTERNAL_SERVICE_SECRETRequiredProtects the internal and operator recovery routes; missing, short or wrong values get a 404Generate an independent string of at least 32 characters
RESEND_API_KEYOptionalDelivers outbox emails in productionResend dashboard
STRIPE_SECRET_KEYRequired for billingReconciles invoices and charges during webhook projection, and runs product synchronization queued by the admin appSame key as the web Worker

Non-secret vars in wrangler.jsonc

These are committed. Edit them in the vars block, and in the env.production.vars block for your deployed Workers.

VariableWorkerLocal defaultPurpose
BETTER_AUTH_URLweb, adminhttp://localhost:3000 (web), http://localhost:3001 (admin)Better Auth base URL; set to each Worker's public origin in production
NEXT_PUBLIC_SITE_URLweb, adminhttp://localhost:3000 (web), http://localhost:3001 (admin)Each Worker's own origin, trusted origin for auth and the MCP issuer; public
NEXT_PUBLIC_ADMIN_APP_URLwebhttp://localhost:3001Where the impersonation banner sends admins back to (/app-admin/users on the admin origin)
NEXT_PUBLIC_WEB_APP_URLadminhttp://localhost:3000The customer app's origin; impersonation redeems its token at <web origin>/auth/impersonate
ADMIN_APPadmin"true"Switches the shared code into admin mode
ADMIN_EMAILweb, admin, platformadmin@example.comThe first account registered with this email gets the admin role; also the default sender address
USE_LOCAL_EMAILweb, admin, platformtrueWhen true, auth emails are also posted to Mailpit; set to false in production
MAILPIT_API_URLweb, admin, platformhttp://localhost:8025Mailpit API used for local delivery; empty in production
INTERNAL_SERVICE_PRINCIPALplatformnextbase-operator-apiNon-secret identity recorded for operator actions
MAIL_FROMplatformnot set locallyOptional verified sender for production emails; falls back to the payload sender or ADMIN_EMAIL

NEXT_PUBLIC_SITE_URL is also needed at build time because it ends up in the browser bundle. The web Worker's cf:build:production script refuses to run until it is set in the shell; the admin Worker's cf:build:production script sets its own NEXT_PUBLIC_SITE_URL and NEXT_PUBLIC_WEB_APP_URL inline in apps/admin/package.json. See deployment.

apps/web/.env.local

Copy from apps/web/.env.local.example. It repeats the Worker values above and adds the ones below. Variables starting with NEXT_PUBLIC_ are exposed to the browser.

VariableRequiredPublicPurposeWhere to get it
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYRequired for billingYesStripe.js on the clientStripe dashboard
NEXT_PUBLIC_POSTHOG_API_KEYOptionalYesPostHog analyticsPostHog project settings
NEXT_PUBLIC_POSTHOG_HOSTOptionalYesPostHog ingestion hostPostHog project settings
NEXT_PUBLIC_GA_IDOptionalYesGoogle Analytics measurement IDGoogle Analytics
NEXT_PUBLIC_SENTRY_DSNOptionalYesSentry error reportingSentry project settings
SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKENOptionalNoSource map upload during next buildSentry organization settings
LOG_LEVELOptionalNoPino log level, default infoYour choice

The repeated entries (NEXT_PUBLIC_SITE_URL, BETTER_AUTH_URL, BETTER_AUTH_SECRET, ADMIN_EMAIL, REALTIME_TOKEN_SECRET, the Stripe keys, USE_LOCAL_EMAIL, MAILPIT_API_URL, RESEND_API_KEY, OPENAI_API_KEY, the social provider keys and REVALIDATE_SECRET) should match what you put in .dev.vars and wrangler.jsonc. At runtime inside the Worker, the binding values win. The admin app has no .env.local.example; its Sentry build settings come from the shell when you build it.

Shell variables for scripts

VariableUsed byDefault
MAILPIT_SMTP_PORT, MAILPIT_HTTP_PORTdocker-compose.yml1025, 8025
PLATFORM_WORKER_PORTscripts/dev-platform-worker.sh, scripts/preview-cloudflare.sh8788
PLATFORM_PERSIST_TOscripts/dev-platform-worker.shapps/web/.wrangler/state
NEXT_PUBLIC_SITE_URLpnpm --filter web cf:build:production and cf:deploy:productionnone; the build fails fast when it is missing
D1_HTTP_URLapps/database/drizzle.config.tsthe local SQLite file under .wrangler/state/v3/d1

Bindings, not variables

D1, R2, queues, Durable Objects, service bindings and rate limiters are declared in the three wrangler.jsonc files and arrive in env next to the variables above. The web and admin Workers each expect APP_DB, NEXT_TAG_CACHE_D1, MEDIA_BUCKET, NEXT_INC_CACHE_R2_BUCKET, PLATFORM, WORKER_SELF_REFERENCE, NEXT_CACHE_DO_QUEUE, AUTH_RATE_LIMITER and API_RATE_LIMITER. The platform Worker expects APP_DB, MEDIA_BUCKET, JOBS_QUEUE and REALTIME. Keep those names; change only the resource names and IDs behind them.

Installation

Clone the Next.js + Cloudflare D1 starter, create the local D1 databases and run the web app, the admin app and the platform Worker on your machine.

Authentication

How Better Auth is configured in the Next.js + Cloudflare D1 starter, how admins are assigned, where the admin app lives, and what protects application data.

On this page

apps/web/.dev.varsapps/admin/.dev.varsapps/platform-worker/.dev.varsNon-secret vars in wrangler.jsoncapps/web/.env.localShell variables for scriptsBindings, not variables