Deploying to Cloudflare
The Cloudflare Workers that host OPB Brain, the bindings they carry, and the commands that deploy them
OPB Brain runs entirely on Cloudflare. There is no Vercel project, no Neon database and no Vercel
Blob store; the four Next.js surfaces are Cloudflare Workers built by
@opennextjs/cloudflare, two more ship as static assets, and
every stateful dependency is a Worker binding rather than a connection string in the
environment.
| Worker | Source | Serves |
|---|---|---|
opb-brain-web | apps/web | Marketing site |
opb-brain-app | apps/app | Authenticated product surface |
opb-brain-api | apps/api | Health, cron and webhook receivers |
opb-brain-docs | docs | This documentation site |
opb-brain-storybook | apps/storybook | Static component workshop |
opb-brain-email | apps/email | Static React Email template preview |
Every directory holding a wrangler.jsonc is deployed and belongs in that table. The
documentation site is the easiest one to miss: it is a separate Next.js project at the repository
root with its own bun.lock, outside the Bun workspace, and it still ships as a Worker through the
same OpenNext adapter, subject to the same plan requirement.
Account: 70c4540a5eb026aed7cf92fd830f00da. Zone available for custom domains: reativa.dev.
Bindings, not connection strings
This is the single biggest change from the previous hosting. A binding is handed to the Worker by the runtime; it cannot be expressed as an environment variable, and there is nothing to leak.
| Binding | Type | Resource | Read through |
|---|---|---|---|
DB | D1 | opb-brain (26d4f07f-b4fc-4cfe-8529-b83972a5f499) | @repo/database |
BLOB | R2 | opb-brain-blob | @repo/storage |
NEXT_INC_CACHE_KV | KV | 39d424cd53e041878bd0289cf42b242f | OpenNext incremental cache |
ASSETS | Static assets | .open-next/assets | Next.js static output |
DATABASE_URL and BLOB_READ_WRITE_TOKEN no longer exist in any request path. DATABASE_URL
survives only as optional local-tooling input for prisma generate and the integration suite.
The deployment contract
Each app's wrangler.jsonc sets three plain variables that replace the VERCEL* block the code
used to read:
| Variable | Value | Read by |
|---|---|---|
DEPLOY_PLATFORM | cloudflare | @repo/next-config; gates Sentry wiring |
DEPLOY_ENV | development | preview | production | @repo/next-config |
DEPLOY_URL | the Worker's own URL | @repo/next-config |
There is no region variable: a Worker has no single region. Public URLs come from the
NEXT_PUBLIC_*_URL values, as they always did — @repo/seo now resolves metadataBase from
NEXT_PUBLIC_WEB_URL instead of a platform-injected hostname.
Deploying
cd apps/api # or apps/web, apps/app, docs
bunx opennextjs-cloudflare build # Next build + Worker bundle into .open-next/
bunx opennextjs-cloudflare preview # run it locally in workerd, not Node
bunx opennextjs-cloudflare deploy # ship itpreview is the one that matters before shipping: next dev runs in Node, and the deployed Worker
runs in workerd. Only preview exercises the runtime you are actually deploying to.
Storybook and the email preview are static builds with no server and no workerd step, so
bun run deploy is the whole contract:
cd apps/storybook && bun run deploy # storybook build && wrangler deploy
cd apps/email && bun run deploy # email export … && wrangler deployRuntime requirements
Every Worker config sets compatibility_flags: ["nodejs_compat"] and a compatibility_date of
2024-09-23 or later. The OpenNext adapter refuses to run without both.
Edge middleware, not proxy.ts
apps/web and apps/app deliberately use middleware.ts, not Next.js 16's proxy.ts. A
proxy.ts always runs on the Node.js runtime and Next refuses to let that be configured, and the
Cloudflare adapter cannot deploy a Node.js proxy — it fails the build with "Node.js middleware is
not currently supported". Next's own upgrade guide gives this instruction: keep middleware.ts
when you need the edge runtime.
Database migrations
The schema has one source of truth, packages/database/prisma/schema.prisma, and one migrations
directory, packages/database/migrations/, which both Prisma and D1 read.
bun run migrate:diff # render SQL for the schema change into the migrations directory
bun run migrate # apply to the local D1 instance
bun run migrate:deploy # apply to the remote D1 databaseD1 is SQLite. The schema already declared relationMode = "prisma" under Postgres, so no
database-enforced foreign key was lost in the move.
Secrets
Runtime secrets are set per Worker, never committed and never echoed:
cd apps/api && bunx wrangler secret put CRON_SECRETThe encrypted workspace bundle (bun run env:decrypt / env:encrypt) remains the workstation
source of truth for local development.
Scheduled work
apps/api/wrangler.jsonc declares the schedule that used to live in vercel.json:
"triggers": {
"crons": ["0 1 * * *"]
}apps/api/worker.ts is the Worker entrypoint. It re-exports OpenNext's generated fetch verbatim
and adds a scheduled handler that calls /cron/keep-alive with the CRON_SECRET bearer token,
so the route keeps one implementation and one authorization check.
Delivery
GitHub Actions cannot run jobs on this repository's account, so CI-driven deploys are not
available. Delivery is operator-run bun run deploy from a workstation, per Worker, after
local gates are green. No branch is wired to a Cloudflare Workers Build: every deployment on
record was an upload from a workstation, and merging to main ships nothing on its own. Never
treat a git push as the production cutover.
Plan requirements
Two pieces of the topology need a payment method on the Cloudflare account:
- Workers Paid ($5/month) — the free plan caps a Worker script at 3 MiB gzipped. The three
product Workers measure 3.86 MiB (
api), 4.18 MiB (web) and 6.50 MiB (app); all three fit the paid 10 MiB limit comfortably. The static-asset Workers (opb-brain-storybook,opb-brain-email) deploy on the free plan. - R2 — activating object storage requires a card on file even though the plan itself is free (10 GB, 1M Class A, 10M Class B included).
D1, KV and static-asset Workers need neither.
What is not deployed
apps/studio (Prisma Studio, port 3005) and apps/docs (a Mintlify project inherited from the
template and superseded by this site) are local tools with no wrangler.jsonc and no Worker.