Skip to content

Deploying Next.js to Cloudflare Workers with OpenNext

Kevin TrinhApril 22, 2026

What I learned moving kevintrinh.dev off Vercel and onto Cloudflare Workers via OpenNext — the tradeoffs, the gotchas, and the parts that just work.

Last updated on April 26, 2026

Cloudflare dashboard showing Workers

Moving a Next.js 16 site to Cloudflare Workers via OpenNext was mostly painless — but the rough edges are worth writing down.

Why move at all?

The honest answer: I wanted my whole stack on one provider. Cloudflare already runs DNS, edge caching, KV, R2, D1, and tunnels for me. Putting the website there too means one dashboard, one billing surface, one set of logs.

What works out of the box

  • App Router pages, both static and dynamic
  • revalidate (ISR via KV)
  • Image optimization (with next/image)
  • Route handlers (app/api/*/route.ts)
  • Middleware
  • Static assets

What doesn't (yet)

The biggest gotcha: next/og (the dynamic OG image generator) doesn't run on the Workers edge runtime as of writing. I've fallen back to a static OG image while waiting on upstream support.

// app/layout.tsx
openGraph: {
  images: [
    { url: "/images/og.png", width: 1200, height: 630 },
  ],
},

Also worth knowing:

  • process.cwd() works at build time but not runtime — file-based config has to be bundled.
  • The KV incremental cache occasionally needs a manual purge after big content changes.
  • Direct-eval warnings during the bundle step are noisy but harmless.

Wrangler setup that worked

// wrangler.jsonc
{
  "name": "kevintrinh-dev",
  "main": ".open-next/worker.js",
  "compatibility_date": "2025-03-01",
  "compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
  "assets": {
    "directory": ".open-next/assets",
    "binding": "ASSETS"
  },
  "kv_namespaces": [
    { "binding": "NEXT_INC_CACHE_KV", "id": "YOUR_KV_ID" }
  ]
}

Deploy is a one-liner once secrets are set:

npm run cf:build && npm run cf:deploy

What I'd tell my past self

  • Move secrets to wrangler secret put early. .env.local only works for local dev.
  • Skip dynamic OG until upstream lands it. Static images are fine.
  • Trust the KV cache, but verify after big deploys. Hit the live URLs with curl -I and check x-nextjs-cache: HIT.