Deployment
The API is a Cloudflare Worker (cloudflare-module). The web app is a static Vite build. There is no first-party “one click” deploy script that ships both.
scripts/deploy/Dockerfile and .gitpod.yml are stale (nginx + playground/dist, pnpm run dev:play, port 5555). create-tamandeletesscripts/deploy/** and .gitpod.yml from generated projects. Do not follow them.1. Build the API
pnpm nx run api:build
Nitro (cloudflare.deployConfig: true) emits Worker assets and wrangler config. Worker name in the source repo is taman-better-auth-back. Generated projects rename it to <name>-api. Renaming a deployed worker creates a new worker — keep the name stable after first deploy.
Then, from the API output directory Nitro documents for your Nitro version:
npx wrangler deploy
deployConfig). Confirm against the build log; this page does not guess a path that drifts with Nitro betas.2. Cloudflare resources
Create (names from nitro.config.ts, rewritten on generate):
| Resource | Source name | Generated |
|---|---|---|
| Durable Object class | TodoPublisherObject | unchanged |
| DO binding | TODO_PUBLISHER | unchanged |
| Queue | taman-email | <name>-email |
| DLQ | taman-email-dlq | <name>-email-dlq |
send_email binding | EMAIL | unchanged |
SQLite DO migration tag: v1 / new_sqlite_classes: ['TodoPublisherObject']. Applying this twice on an existing worker can fail — treat it as one-time.
Unverified in-repo: dashboard steps to attach Hyperdrive / TCP Postgres, Email Routing destination, and queue production settings. Use Cloudflare docs for those products.
3. Secrets and vars
The API reads these values only through useRuntimeConfig(), and Nitro fills runtimeConfig from NITRO_* variables. Set the Worker secrets / vars with the same names as apps/api/.env:
NITRO_DATABASE_URLNITRO_TRUSTED_ORIGINS— your production web origin(s), comma-separatedNITRO_BASE_URL— public API URLNITRO_APP_URL— public web URLNITRO_BETTER_AUTH_SECRETNITRO_GOOGLE_CLIENT_ID/NITRO_GOOGLE_CLIENT_SECRET(optional)NITRO_EMAIL_FROM/NITRO_EMAIL_FROM_NAME
An unprefixed name such as DATABASE_URL is ignored by the Worker and leaves the value empty. After the first deploy, check that /readyz returns OK.
Google Cloud Console: authorized redirect {NITRO_BASE_URL}/api/auth/callback/google.
4. Database
Do not use drizzle-kit push in production. Generate migrations (pnpm db:generate), commit them, run pnpm db-pg:migrate:prod from a trusted network (or CI). drizzle-kit reads the production URL from an unprefixed DATABASE_URL, unlike the Worker.
Postgres must be reachable from the Worker (Hyperdrive, Cloudflare Tunnel, or a publicly reachable instance with a tight allow-list). How you expose Postgres is not specified in the template.
5. Web static files
pnpm --filter @taman/web build
Set VITE_API_URL to the Worker URL, VITE_ROUTER_HISTORY (hash is the production example — useful if you cannot configure SPA fallbacks), VITE_BASE if not serving at /.
Host apps/web/dist on Cloudflare Workers Static Assets, Pages, or any static host. Configure HTTPS, and put that origin in NITRO_TRUSTED_ORIGINS.
Production builds may emit _app-config-[version]-[hash].js (window._TAMAN_ADMIN_DEV_CONFIG_) so VITE_GLOB_* can change without rebuilding — see vite-config.
6. Cookies and CORS
better-auth cookies use prefix taman. The SPA must call the API with credentials: 'include'. NITRO_TRUSTED_ORIGINS must list exact origins (https://app.example.com, not *). Empty config yields no allowed origins (resolveTrustedOrigins returns []).