Next.js runs on Cloudflare Workers in one of two ways. The OpenNext adapter (@opennextjs/cloudflare) wraps the output of a normal next build for the Workers runtime and is the stable choice. vinext reimplements the Next.js API on Vite instead; it builds faster and ships less JavaScript, but it is still in beta as of September 2026.
Cloudflare’s own Next.js framework guide now says vinext is “the default way to run Next.js applications on Cloudflare Workers”. Read the next sentence in that guide too: it is “not yet a drop-in replacement for every application or production workload”. Both statements are true, and which one matters depends on whether you are starting a project or moving one that already earns money.
Why are there two adapters at all?
The first attempt was @cloudflare/next-on-pages, which only worked if every route opted into the Edge runtime. That ruled out most of the Node ecosystem, so in April 2025 Cloudflare shipped the OpenNext adapter at 1.0 beta and called it “the preferred way to deploy Next applications to the Cloudflare platform, instead of Next on Pages”. OpenNext runs the Node runtime build of Next.js on top of the nodejs_compat flag, so fs, crypto, stream and friends behave.
Then in March 2026 Cloudflare published vinext, an experimental rewrite of the Next.js API surface as a Vite plugin. Six months on it has reached 1.0.0-beta.9, and the vinext README puts the difference plainly: OpenNext “adapts the output of a standard next build”, whereas vinext “reimplements the Next.js APIs on Vite from scratch, which means faster builds and smaller bundles, but less coverage of the long tail of Next.js features”.
If you still have next-on-pages in a project, that is the one to remove. Neither current path uses it.
How do you deploy Next.js on Cloudflare Workers with OpenNext?
Install the adapter and a recent Wrangler (the get started guide requires 3.99.0 or later), then add a wrangler.jsonc:
npm install @opennextjs/cloudflare@latest
npm install --save-dev wrangler@latest
{
"$schema": "node_modules/wrangler/config-schema.json",
"main": ".open-next/worker.js",
"name": "my-app",
"compatibility_date": "2024-12-30",
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
"assets": { "directory": ".open-next/assets", "binding": "ASSETS" },
"services": [{ "binding": "WORKER_SELF_REFERENCE", "service": "my-app" }],
"images": { "binding": "IMAGES" }
}
The WORKER_SELF_REFERENCE service binding is not optional. The caching docs list it as required for every setup, because the adapter calls back into its own Worker to run revalidation.
Next to it goes open-next.config.ts, which is where you choose cache storage:
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
export default defineCloudflareConfig({
incrementalCache: r2IncrementalCache,
});
Add the scripts, and one line to next.config.ts so next dev can see your bindings:
{
"build": "next build",
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
"deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
}
import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
initOpenNextCloudflareForDev();
Then delete every export const runtime = "edge" in the codebase. The adapter only supports the Node runtime, and a leftover edge export is the most common reason a first build fails. Add .open-next to .gitignore and you are done.
Which cache backend should OpenNext use?
Three decisions, each with a binding name the adapter expects. The caching page is the source for all of them.
The incremental cache holds ISR and SSG output. Use R2 (NEXT_INC_CACHE_R2_BUCKET). KV is offered but the docs say outright “We do not recommend using KV because it is eventually consistent”, which is a bad property for a cache that Next.js expects to read back what it just wrote. A site with no revalidation at all can use the static-assets cache instead, which is read-only and free.
The tag cache backs revalidateTag and revalidatePath. D1 (NEXT_TAG_CACHE_D1) is fine for small sites; the sharded Durable Object cache is for anything with real traffic or frequent purges. Pages Router apps that never call those functions can skip it.
The queue de-duplicates time-based revalidations. Use the Durable Object queue (NEXT_CACHE_DO_QUEUE) in production; the in-memory one is per isolate and the docs say it “is not fully suitable for production”. A complete small-site config looks like this:
import { defineCloudflareConfig } from "@opennextjs/cloudflare";
import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
import doQueue from "@opennextjs/cloudflare/overrides/queue/do-queue";
import d1NextTagCache from "@opennextjs/cloudflare/overrides/tag-cache/d1-next-tag-cache";
export default defineCloudflareConfig({
incrementalCache: r2IncrementalCache,
queue: doQueue,
tagCache: d1NextTagCache,
});
If you are unsure what those three Cloudflare stores actually are, our KV, D1 and Durable Objects comparison covers the consistency differences that make R2 and D1 the right defaults here.
What does vinext do differently?
vinext is a Vite plugin. It resolves every next/* import to a shim that reimplements that module on web standards and React primitives, scans app/ and pages/ itself, and uses @vitejs/plugin-rsc for Server Components. Your project keeps its app/, pages/, public/ and next.config.js; the next package stops being what runs them.
The requirements are strict. It targets Next.js 16 only, needs vite ^8.0.0 and react ^19.2.6, and has no interest in APIs deprecated before 16. For an existing app the workflow is:
npx vinext check
npx vinext init
npm run dev:vinext
npm run build:vinext
npx @vinext/cloudflare deploy
vinext check scans the project for APIs it cannot run before you change anything, which is the right order. On Cloudflare with the App Router you will end up with a vite.config.ts like this:
import { defineConfig } from "vite";
import vinext from "vinext";
import { cloudflare } from "@cloudflare/vite-plugin";
export default defineConfig({
plugins: [
vinext(),
cloudflare({
viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] },
}),
],
});
The vinext site claims up to twice the build speed of Next.js 16 with Turbopack and roughly a third smaller client bundles, on the project’s own 33-route benchmark. Those are Cloudflare’s numbers, not independent ones, and the March launch post described its own figures as “directional, not definitive”. The interesting claim is the coverage one: the compatibility dashboard runs Next.js’s own test suite against vinext and on 8 September 2026 reported 93.9% of the supported surface covered, with a 99.5% pass rate within that surface, tested against Next.js 16.2.6 while next@latest is 16.3.4.
The README’s list of active gaps is the part to read before adopting it: cache components and "use cache", Partial Prerendering, build-time image and font optimisation, native modules in App Router development, and the runtime and preferredRegion segment options. The August and September beta release notes are largely cache work ("use cache" entries varied by root params, cache warming for static route handlers), so that list is shrinking, but if your app already depends on the use cache directive then today the answer is OpenNext.
What breaks on Workers whichever adapter you use?
The runtime is the same either way, so its limits apply to both.
CPU time is the one that surprises people. On the Free plan a request gets 10 ms of CPU; a server-rendered React page will often exceed that, so treat SSR on Cloudflare as a Paid-plan feature (30 s by default, configurable to five minutes). The Worker must also finish its top-level code within one second of startup, which punishes heavy module-scope initialisation.
Bundle size is less of a problem than older write-ups suggest. The Workers limits page now states “There is no compressed size limit. Only the uncompressed bundle size counts”, at 64 MiB on both plans. The OpenNext docs still quote the old 3 MiB and 10 MiB gzip figures, so trust the limits page.
Node middleware (the runtime: "nodejs" option for middleware.ts / proxy.ts introduced in Next.js 15.2) is listed as “not yet supported” by OpenNext. Native .node addons will not load under either adapter. And next/image optimisation on Workers goes through the Cloudflare Images binding rather than the built-in optimiser, which is why "images": { "binding": "IMAGES" } sits in the config above.
Which one would we use?
For an existing production Next.js app, OpenNext. It is at 1.20, it supports the latest Next.js 15 minor and every 16.x release, its caching story is documented down to the binding names, and its failure modes are known. The migration is mostly configuration plus deleting edge-runtime exports, and you can do it in a day. If you were already deciding between Cloudflare products rather than adapters, our Pages versus Workers post explains why Workers is the target for both of these.
For a new project on Next.js 16 that you control end to end, vinext is worth the beta label. Run vinext check first, keep the dashboard open, and avoid cache components until the README stops listing them as a gap. The build-time win is real enough to matter on a CI bill, and the smaller client bundle helps everywhere.
We would not put vinext under a revenue-bearing storefront yet, and we would not start a new Cloudflare project on next-on-pages at all.