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.
Local development doesn't need a Cloudflare account. Wrangler keeps local D1 databases, R2 buckets, queues and Durable Objects under apps/web/.wrangler/state, and the platform Worker reads from that same directory.
Use Node.js 24 and the pnpm version in the repository's package.json (pnpm@11.1.2). Docker is optional and only used for the Mailpit inbox.
Clone the kit
git clone https://github.com/usenextbase/nextjs-cloudflare-d1-starter.git
cd nextjs-cloudflare-d1-starterIf GitHub says the repository doesn't exist, check that you are signed in with the account that has access.
Install dependencies
corepack enable
pnpm installpnpm install runs fumadocs-mdx in apps/web as a postinstall step. The lockfile also applies a patch to @opennextjs/aws from patches/; that happens automatically.
Create the env files
Create each file only when it is missing. Never overwrite a file you already customized.
[ -f apps/web/.dev.vars ] || cp apps/web/.dev.vars.example apps/web/.dev.vars
[ -f apps/platform-worker/.dev.vars ] || cp apps/platform-worker/.dev.vars.example apps/platform-worker/.dev.vars
[ -f apps/admin/.dev.vars ] || cp apps/admin/.dev.vars.example apps/admin/.dev.vars
[ -f apps/web/.env.local ] || cp apps/web/.env.local.example apps/web/.env.localThe .dev.vars files hold Worker secrets. Non-secret local defaults such as BETTER_AUTH_URL, NEXT_PUBLIC_SITE_URL, ADMIN_EMAIL, USE_LOCAL_EMAIL and MAILPIT_API_URL are already set in the vars block of apps/web/wrangler.jsonc, apps/admin/wrangler.jsonc and apps/platform-worker/wrangler.jsonc. apps/web/.env.local only holds the optional public analytics and Sentry build variables.
Two values need attention before the app runs well:
REALTIME_TOKEN_SECRETmust be the exact same value of at least 32 characters in the web, admin and platform.dev.varsfiles.INTERNAL_SERVICE_SECRETin the platform Worker must be an independent value of at least 32 characters.
The admin app can keep its own BETTER_AUTH_SECRET; its cookies use a different prefix, so the two sign-ins stay independent. The full list is in environment variables.
Start Mailpit (optional)
pnpm infra:start
pnpm infra:waitThis runs docker compose up -d mailpit and waits for http://localhost:8025/livez. The inbox is at http://localhost:8025 and SMTP listens on port 1025. Override the ports with MAILPIT_SMTP_PORT and MAILPIT_HTTP_PORT in your shell if they clash.
Without Mailpit, auth emails are written to the D1 outbox and stay pending. Sign-up doesn't fail; you just can't open the verification link. Use the seeded accounts in the next step instead.
Migrate and seed the local databases
pnpm db:migrate
pnpm db:seedpnpm db:migrate applies the application migrations from apps/database/migrations to the APP_DB binding and the OpenNext cache migration from apps/web/cache-migrations to NEXT_TAG_CACHE_D1. Both run against local Wrangler databases using apps/web/wrangler.jsonc. The admin app points at the same local database names, so it needs no separate migration step.
pnpm db:seed executes apps/database/seed.sql against the local APP_DB. It is idempotent and creates verified local-only accounts, so you can sign in without an email provider. The app-admin account is admin@example.com with password password123. Alice, Bob and Carol are at alice@demo.nextbase.local, bob@demo.nextbase.local and carol@demo.nextbase.local with the same password. Never reuse these credentials outside local development.
Start the dev servers
pnpm devTurborepo runs every workspace dev script in parallel:
| Process | Address |
|---|---|
Next.js dev server (apps/web) | http://localhost:3000 |
Admin app (apps/admin, next dev -p 3001) | http://localhost:3001 |
Platform Worker through wrangler dev (apps/platform-worker) | http://127.0.0.1:8788, health check at /health |
React Email preview (apps/email) | http://localhost:3001 |
The admin app and the email preview both default to port 3001
apps/admin and apps/email each declare port 3001, so under pnpm dev only one of them gets it. When you need the admin app, run the processes separately: pnpm web#dev, pnpm worker:dev and pnpm admin:dev in three terminals, and start the email preview with pnpm --filter @nextbase/email dev only when you work on templates. NEXT_PUBLIC_ADMIN_APP_URL in apps/web/wrangler.jsonc and NEXT_PUBLIC_WEB_APP_URL in apps/admin/wrangler.jsonc already point the two apps at each other on ports 3000 and 3001.
Both Next.js apps read their Worker bindings and .dev.vars through initOpenNextCloudflareForDev() in their next.config.ts, so getCloudflareContext() works in development. The platform Worker script also polls /__scheduled every two seconds so the outbox cron behaves like production. Change the Worker port with PLATFORM_WORKER_PORT.
Sign up and create a workspace
Open http://localhost:3000. Either sign in with a seeded account or sign up with a new email. New accounts must verify their email; open the message in Mailpit when it is running.
Complete onboarding and create a workspace. Add a project through the website so you have data to try with an agent later. If you sign up with the ADMIN_EMAIL configured in the Wrangler vars (admin@example.com by default), that account receives the application-admin role.
To use the admin area, open http://localhost:3001 and sign in again with the admin account. The admin app has its own login page and its own session cookie; a non-admin account gets a 403 there.
Verify the setup
pnpm verify:localThis runs the legacy-reference scan, the Drizzle journal and schema drift checks, generated-type checks, local migrations, lint, typecheck, unit tests and an OpenNext Worker build. The Playwright suite builds the admin and web Workers and prepares local D1 on its own:
pnpm test-integrationFor a production-runtime check in workerd, run pnpm cf:preview. It builds all three Workers with pnpm cf:build and starts the platform Worker and the web Worker locally with opennextjs-cloudflare preview.
One-command setup
./setup.sh runs the same flow: install, copy apps/web/.dev.vars, apps/platform-worker/.dev.vars and apps/web/.env.local from their examples when missing, start Mailpit if Docker is available, migrate, seed and pnpm verify:local. It doesn't create apps/admin/.dev.vars; copy that one yourself before starting the admin app.
Stop and reset
Press Ctrl-C to stop pnpm dev. Stop Mailpit with pnpm infra:stop; pnpm infra:status shows whether it is running.
To clear the local D1 databases and re-apply migrations:
pnpm db:reset
pnpm db:seeddb:reset deletes only apps/web/.wrangler/state/v3/d1, so local R2 objects and Durable Object state are kept. Delete apps/web/.wrangler entirely if you want a completely fresh local Cloudflare state.
Other local tools
pnpm db:studioopens Drizzle Studio against the local SQLite file.pnpm admin:devruns only the admin app;pnpm web#devruns only the web app;pnpm worker:devruns only the platform Worker.pnpm --filter @nextbase/email devruns only the email preview.stripe listen --forward-to localhost:3000/api/stripe/webhooksforwards Stripe webhooks; copy the printedwhsec_value intoSTRIPE_WEBHOOK_SECRETinapps/web/.dev.varsand restart the web app.