> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy to Cloudflare Workers

> Migrate from Vercel to Workers for sub-50ms global latency.

Cloudflare Workers is the upgrade path when you outgrow Vercel — high-traffic
sites, sub-50ms global p99 latency, or just cheaper egress at scale. The
migration is a single adapter swap (OpenNext for Cloudflare) plus a switch to a
serverless-friendly Postgres pooler. Plan on an afternoon if it's your first
Workers deploy; less if you've shipped Workers before.

## Prerequisites

* A Cloudflare account with Workers enabled (the free plan is enough for a
  staging deploy).
* `wrangler` CLI authenticated: `pnpm dlx wrangler login`.
* Your Cloudflare account ID (dashboard → right sidebar).
* A serverless-friendly Postgres pooler URL — Workers cannot hold long-lived TCP
  connections, so direct DB URLs are out. Good options: **Neon serverless driver**
  (HTTP-based), **Supabase pgbouncer** (transaction mode), or a hosted PgBouncer.
* Your existing env values from the Vercel deploy (you've already shipped on
  Vercel, right?). [Vercel deploy guide](/deployment/vercel) first.

The repo ships a `wrangler.toml.example` at the root — copy it, don't write one
from scratch.

## Step-by-step

### 1. Install the OpenNext Cloudflare adapter

```bash theme={null}
pnpm add -D @opennextjs/cloudflare
```

This bundles your Next.js app into a Worker-compatible build under
`.open-next/worker.js`.

### 2. Create your wrangler.toml

```bash theme={null}
cp wrangler.toml.example wrangler.toml
```

Edit two fields:

* `name` — your Worker's name (lowercase, dash-separated).
* `account_id` — uncomment and paste your Cloudflare account ID.

The shipped config already sets `compatibility_date`, `nodejs_compat` flag, the
`.open-next/assets` binding, and an `[assets]` block. Don't change those unless
you know why.

### 3. Switch to a serverless-friendly DB pooler

Workers run for milliseconds and tear down — the classic `pg` driver dies on
cold starts. Pick one:

```
# Neon — uses HTTP, no connection pool needed
DATABASE_URL=postgresql://user:pass@ep-xyz.neon.tech/db?sslmode=require

# Supabase — pgbouncer in transaction mode (port 6543)
DATABASE_URL=postgresql://user:pass@db.xyz.supabase.co:6543/postgres?pgbouncer=true
```

If you're staying on Drizzle (you are — it's the only ORM in the scaffold), you
may need to swap the driver in `src/db/index.ts` to `drizzle-orm/neon-http` or
`drizzle-orm/postgres-js` configured for pooled mode. The `pg` driver does not
work on Workers.

### 4. Push secrets via wrangler

**Never** commit secrets to `wrangler.toml`. Push each one:

```bash theme={null}
wrangler secret put DATABASE_URL
wrangler secret put BETTER_AUTH_SECRET
wrangler secret put STRIPE_SECRET_KEY
wrangler secret put STRIPE_WEBHOOK_SECRET
wrangler secret put RESEND_API_KEY
# …repeat for every server-side env var you use
```

Public vars (the `NEXT_PUBLIC_*` ones) live under `[vars]` in `wrangler.toml` —
they're shipped in the client bundle anyway, so no harm.

### 5. Build and deploy

```bash theme={null}
pnpm exec opennextjs-cloudflare build
pnpm exec wrangler deploy
```

The first build is slow (\~3-5 minutes) because OpenNext ahead-of-time bundles
every page. Subsequent builds incremental-cache better.

The deploy URL prints to stdout — it'll be
`https://<your-worker-name>.<your-subdomain>.workers.dev`. Custom domains attach
through the Cloudflare dashboard (Workers & Pages → your worker → Custom Domains).

### 6. Re-point payment webhooks

Same drill as Vercel: in your payment provider's dashboard, change the webhook
endpoint URL to the new Workers domain. Verify the signing secret you pushed via
`wrangler secret put` matches the value the dashboard expects.

## Verify it works

Identical checklist to the Vercel deploy:

* `https://your-worker.workers.dev/` — home renders
* `https://your-worker.workers.dev/api/ping` — `{ ok: true }`
* `https://your-worker.workers.dev/sitemap.xml` — populated
* A real \$1 test charge → `payment` row appears within 5s.

Workers gives you per-request CPU-time metrics in the dashboard. If you see
sustained > 50ms CPU times on simple pages, you have a misconfigured DB driver
holding connections.

## Common pitfalls

* **Long-lived DB connections.** The number-one mistake. Workers cannot hold a
  pool — every request is a new isolate, and TCP sockets vanish at the end. Use
  Neon's HTTP driver or Supabase's pgbouncer URL. Symptom: random 522 / 524
  errors under load.
* **Missing `nodejs_compat` flag.** Already present in the shipped config; if you
  removed it, webhook signature verification (`node:crypto`) explodes at runtime.
* **Node-only deps.** Audit your dependencies with `pnpm dlx knip` before
  deploying. Anything that imports `fs`, `child_process`, `net`, or
  `worker_threads` will silently break. The scaffold itself is clean — third-party
  libs you add are the risk.
* **KV consistency confusion.** Workers KV is *eventually* consistent. Don't use
  it as the source of truth for auth sessions or payment idempotency. Postgres
  remains the source of truth for everything ledger-shaped.
* **Wrangler `vars` vs `secrets`.** `[vars]` are public (shipped in source).
  `wrangler secret put` is encrypted. Putting `STRIPE_SECRET_KEY` in `[vars]` is
  a wallet-emptying mistake.
* **Deploy size limits.** Free Workers plan caps at 1 MB compressed. OpenNext
  hits this fast — bump to the Paid plan (\$5/mo, 10 MB) before you ship anything
  with images or fonts.

## Official docs

* Cloudflare Workers: [developers.cloudflare.com/workers](https://developers.cloudflare.com/workers/)
* OpenNext Cloudflare adapter: [opennext.js.org/cloudflare](https://opennext.js.org/cloudflare)
* Wrangler CLI reference: [developers.cloudflare.com/workers/wrangler](https://developers.cloudflare.com/workers/wrangler/)
* Neon serverless driver: [neon.tech/docs/serverless/serverless-driver](https://neon.tech/docs/serverless/serverless-driver)
* Supabase pgbouncer: [supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler)
