Next.js on Cloudflare via OpenNext
OpenNext lets you deploy Next.js apps to Cloudflare Workers without rewriting your app. It supports App Router, Pages Router, RSC, ISR, streaming, Server Actions, and more. The main trade-off is no workerd dev parity: development runs in Node.js.
Setup
OpenNext isn’t scaffolded by create-cloudflare. You add it to an existing Next.js project.
# Start with a Next.js app
npx create-next-app@latest my-next-app
cd my-next-app
# Add OpenNext
npm install @opennextjs/cloudflare
# Create worker entry point
touch worker.ts
Worker Entry Point
// worker.ts
import { initOpenNextCloudflareHandler } from "@opennextjs/cloudflare";
const handler = await initOpenNextCloudflareHandler();
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
return handler(request, env, ctx);
},
};
OpenNext Config
// open-next.config.ts
import type { OpenNextConfig } from "@opennextjs/cloudflare";
const config: OpenNextConfig = {
default: {
override: {
wrapper: "cloudflare-node",
converter: "edge",
},
},
};
export default config;
Wrangler Config
// wrangler.jsonc
{
"name": "my-next-app",
"main": ".open-next/worker.js",
"compatibility_flags": ["nodejs_compat"],
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS"
},
"kv_namespaces": [
{
"binding": "NEXT_CACHE_WORKERS_KV",
"id": "your-kv-id"
}
]
}
Accessing Cloudflare Bindings
// app/page.tsx (Server Component)
import { getRequestContext } from "@opennextjs/cloudflare";
export default async function Home() {
const { env, ctx } = getRequestContext();
const value = await env.MY_KV.get("greeting");
return <h1>{value || "Hello from Next.js on Cloudflare"}</h1>;
}
// app/api/data/route.ts (Route Handler)
import { getRequestContext } from "@opennextjs/cloudflare";
export async function GET() {
const { env } = getRequestContext();
const data = await env.MY_KV.get("key");
return Response.json({ data });
}
Build and Deploy
# Build with OpenNext
npx @opennextjs/cloudflare build
# Preview locally (runs in workerd)
npx wrangler dev
# Deploy
npx wrangler deploy
Gotcha: The build step is
@opennextjs/cloudflare build, notnext build. OpenNext wraps the Next.js build and transforms the output for Workers.
Supported Next.js Features
| Feature | Status |
|---|---|
| App Router | Supported |
| Pages Router | Supported |
| React Server Components | Supported |
| Server Actions | Supported |
| ISR (Incremental Static Regeneration) | Supported (needs KV) |
| Streaming | Supported |
'use cache' | Supported |
| Middleware | Supported |
| Image Optimization | Partial (needs custom loader) |
| PPR (Partial Prerendering) | Supported |
| Turbopack | Supported |
What Doesn’t Work
- Dev/prod parity:
next devruns Node.js. You won’t catchworkerdissues until you runwrangler devafter building. next/imagedefault loader: The default loader requires a Node.js server. Use Cloudflare Images or a custom loader.- Node.js APIs without polyfills: Some dependencies using
fs,net, orchild_processwill fail. - Edge Runtime inconsistencies: Some Next.js edge middleware patterns behave differently on Workers.
Key Points
- Supports Next.js 14, 15, and 16
getRequestContext()for binding access (notprocess.env)- ISR requires a KV namespace bound as
NEXT_CACHE_WORKERS_KV - Build with
@opennextjs/cloudflare build, notnext build - Best for: migrating existing Next.js apps to Cloudflare without a rewrite
- Not ideal for: new projects (React Router v7 or TanStack Start have better Cloudflare integration)
Gotcha: OpenNext is a community project, not maintained by Vercel or Cloudflare directly. While it’s well-supported, there’s inherent lag between Next.js releases and OpenNext adapter updates.