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.
Prerequisites
Section titled “Prerequisites”- A Cloudflare account with Workers + Pages enabled.
wranglerauthenticated (npx wrangler login).- A Neon project with
DATABASE_URLready. 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):
npx wrangler r2 bucket create sadie-uploadsnpx wrangler r2 bucket create sadie-opennext-cacheBuild + deploy
Section titled “Build + deploy”From the repo root:
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 buildproduces the standard Next output.@opennextjs/cloudflare buildreadsapps/app/open-next.config.ts, rewrites the output into.open-next/, wires up R2 incremental cache + Durable Object tag cache + DO queue.wrangler pages deploypushes.open-next/assetswith the worker pointed at.open-next/worker.jsas declared inapps/app/wrangler.jsonc.
Required environment variables (Pages)
Section titled “Required environment variables (Pages)”Set these in the Cloudflare dashboard under Workers & Pages → sadie → Settings → Variables (or via wrangler pages secret put for secrets).
| Var | Note |
|---|---|
DATABASE_URL | Neon pooled connection string. |
SADIE_ENCRYPTION_KEY | 32-byte hex or base64. Required in production. The app refuses to boot without it. |
SADIE_CRON_SECRET | Required if you run the scheduler worker. |
SADIE_FROM_EMAIL | Required for transactional email. Must be an address on your Cloudflare Email Sending domain. |
ANTHROPIC_API_KEY / OPENAI_API_KEY | At 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-indexeddbpersists drafts locally. SetNEXT_PUBLIC_COLLAB_WS_URLto 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:1234and restartwrangler pages deployso the updated client picks it up.
A Cloudflare Durable Object port of the collab server is a reasonable future, but not today’s deploy.
Optional: scheduler worker
Section titled “Optional: scheduler worker”Compile runs, feed refresh, and live ingestion fire on a cron. The scheduler is a separate Cloudflare Worker at scripts/cloudflare/worker.ts.
cd scripts/cloudflarenpx wrangler secret put SADIE_CRON_SECRETnpx wrangler secret put SADIE_APP_URL # e.g. https://sadie.example.comnpx wrangler deployOn 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).
Custom domain
Section titled “Custom domain”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.
Verifying a deploy
Section titled “Verifying a deploy”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.