Introduction

OverviewArchitectureAgent Experience

The product

This repository

Structure

Usage

Other

Deployment

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.

WorkerSourceServes
opb-brain-webapps/webMarketing site
opb-brain-appapps/appAuthenticated product surface
opb-brain-apiapps/apiHealth, cron and webhook receivers
opb-brain-docsdocsThis documentation site
opb-brain-storybookapps/storybookStatic component workshop
opb-brain-emailapps/emailStatic 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.

BindingTypeResourceRead through
DBD1opb-brain (26d4f07f-b4fc-4cfe-8529-b83972a5f499)@repo/database
BLOBR2opb-brain-blob@repo/storage
NEXT_INC_CACHE_KVKV39d424cd53e041878bd0289cf42b242fOpenNext incremental cache
ASSETSStatic assets.open-next/assetsNext.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:

VariableValueRead by
DEPLOY_PLATFORMcloudflare@repo/next-config; gates Sentry wiring
DEPLOY_ENVdevelopment | preview | production@repo/next-config
DEPLOY_URLthe 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 it

preview 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 deploy

Runtime 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 database

D1 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_SECRET

The 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:

apps/api/wrangler.jsonc
"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.