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:deployWhat I'd tell my past self
- Move secrets to
wrangler secret putearly..env.localonly 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 -Iand checkx-nextjs-cache: HIT.