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, not next build. OpenNext wraps the Next.js build and transforms the output for Workers.

Supported Next.js Features

FeatureStatus
App RouterSupported
Pages RouterSupported
React Server ComponentsSupported
Server ActionsSupported
ISR (Incremental Static Regeneration)Supported (needs KV)
StreamingSupported
'use cache'Supported
MiddlewareSupported
Image OptimizationPartial (needs custom loader)
PPR (Partial Prerendering)Supported
TurbopackSupported

What Doesn’t Work

  • Dev/prod parity: next dev runs Node.js. You won’t catch workerd issues until you run wrangler dev after building.
  • next/image default 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, or child_process will 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 (not process.env)
  • ISR requires a KV namespace bound as NEXT_CACHE_WORKERS_KV
  • Build with @opennextjs/cloudflare build, not next 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.