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.
  • A Cloudflare Email Sending domain if you want password resets, verification messages, and Studio share invitations. Onboard the domain in the Cloudflare dashboard so Cloudflare can add SPF, DKIM, DMARC, and bounce-handling records.
  • 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.
SADIE_FROM_EMAILRequired for transactional email. Must be an address on your Cloudflare Email Sending domain.
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 live ingestion 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. On top-of-hour ticks it also POSTs to /api/integrations/notion/sync and /api/notetaker/reconcile/fireflies. These 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.