Skip to content

Cloudflare Pages deploy

Sadie deploys to Cloudflare Pages as a single worker. OpenNext compiles the Next.js 15 App Router build into a Cloudflare-compatible bundle; wrangler pages deploy pushes assets. The whole thing is one pnpm deploy command.

  • A Cloudflare account with Workers + Pages enabled.
  • wrangler authenticated (npx wrangler login).
  • A Neon project with DATABASE_URL ready. See Neon.
  • Two R2 buckets (the build references them in wrangler.jsonc):
Terminal window
npx wrangler r2 bucket create sadie-uploads
npx wrangler r2 bucket create sadie-opennext-cache

From the repo root:

Terminal window
pnpm --filter @sadie/app build:cf # runs `next build && @opennextjs/cloudflare build`
pnpm --filter @sadie/app deploy # `wrangler pages deploy .open-next/assets --project-name=sadie --branch=main`

The two commands are bundled as one in the app’s deploy script. pnpm --filter @sadie/app deploy does both. Behind the scenes:

  • next build produces the standard Next output.
  • @opennextjs/cloudflare build reads apps/app/open-next.config.ts, rewrites the output into .open-next/, wires up R2 incremental cache + Durable Object tag cache + DO queue.
  • wrangler pages deploy pushes .open-next/assets with the worker pointed at .open-next/worker.js as declared in apps/app/wrangler.jsonc.

Set these in the Cloudflare dashboard under Workers & Pages → sadie → Settings → Variables (or via wrangler pages secret put for secrets).

VarNote
DATABASE_URLNeon pooled connection string.
SADIE_ENCRYPTION_KEY32-byte hex or base64. Required in production. The app refuses to boot without it.
SADIE_CRON_SECRETRequired if you run the scheduler worker.
ANTHROPIC_API_KEY / OPENAI_API_KEYAt least one, unless your users supply their own keys in Settings.
AI_FRONTIER_* (optional)Pin specific models per tier. Defaults are fine for most deploys.

The collab websocket: the one out-of-process piece

Section titled “The collab websocket: the one out-of-process piece”

Studio multiplayer uses Yjs with a y-websocket server at scripts/collab-server.mjs. Cloudflare Pages is a request/response worker and cannot host a persistent websocket process. Two options:

  • Skip Studio multiplayer. Solo editing works without a websocket; y-indexeddb persists drafts locally. Set NEXT_PUBLIC_COLLAB_WS_URL to an unreachable host (or leave the default) and the client silently falls back to local-only.
  • Run the collab server elsewhere. Any VM or container with a public WS endpoint works. Set NEXT_PUBLIC_COLLAB_WS_URL=wss://your-host:1234 and restart wrangler pages deploy so the updated client picks it up.

A Cloudflare Durable Object port of the collab server is a reasonable future, but not today’s deploy.

Compile runs, feed refresh, and agent evolution fire on a cron. The scheduler is a separate Cloudflare Worker at scripts/cloudflare/worker.ts.

Terminal window
cd scripts/cloudflare
npx wrangler secret put SADIE_CRON_SECRET
npx wrangler secret put SADIE_APP_URL # e.g. https://sadie.example.com
npx wrangler deploy

On its configured cron, the worker POSTs to /api/compile/run and /api/feeds/refresh-all on the app, passing the shared secret as a bearer token. Both endpoints authenticate via SADIE_CRON_SECRET (see apps/app/lib/cron.ts).

apps/app/wrangler.jsonc already declares a route for sadie.wiki. Change that block to your own domain:

"routes": [
{ "pattern": "your-domain.example", "custom_domain": true }
]

Then add the domain in the Cloudflare dashboard and DNS-verify.

After the first deploy, hit the app from any browser. You should land on /onboarding/frame. If you see a Next.js error, check wrangler tail sadie for runtime logs. The most common misconfiguration is a missing SADIE_ENCRYPTION_KEY.