Cloud & DevOpsSeptember 29, 2026•7 min read

Running Next.js 16 on Cloudflare Workers with OpenNext

One worker, three cache layers, and zero origin servers — our production setup end to end.

IE
Intactic Engineering Team
Platform Engineering
Running Next.js 16 on Cloudflare Workers with OpenNext

Next.js 16 on Cloudflare Workers is production-ready with the OpenNext adapter — if you wire the caching correctly. Here is our exact setup: the wrangler bindings, the R2 and D1 cache layers, the Durable Object revalidation queue, and the CI pipeline that rebuilds it all on every push.

The promise of Next.js on Cloudflare Workers is simple: your entire app — routing, rendering, API routes, ISR — becomes one Worker with no origin server behind it. The OpenNext Cloudflare adapter delivers on that promise, but the difference between a demo and production is almost entirely in the caching configuration and the deployment pipeline. This post documents our production setup exactly as it runs today.

The foundation is wrangler.jsonc. One Worker named intactic serves everything. The D1 databases bind as CONTENT_DB for public content and settings and ADMIN_DB for admin authentication and form submissions. R2 binds as MEDIA_BUCKET for user uploads and as a dedicated incremental cache bucket so ISR entries never mix with media. There is also a self-referencing service binding, which sounds exotic but exists for one reason: the Durable Object that drives revalidation calls the Worker through it.

The caching stack is where OpenNext earns its keep, and it maps directly to the official guideline for a small site using revalidation. Incremental cache entries — ISR pages, RSC payloads, and data cache results — live in the dedicated R2 bucket. Tag cache entries for revalidateTag live in a dedicated D1 database, provisioned by an idempotent migration that creates the revalidations table. Time-based revalidation runs through a Durable Object queue on a 300-second cadence, which is why our public pages ship s-maxage windows of roughly two minutes with stale-while-revalidate behind them.

Static assets get their own policy through a _headers file that the Worker applies to asset responses. Fingerprinted Next.js build output is immutable — a full year of max-age with the immutable flag. Unfingerprinted public assets get four hours of freshness with a day of stale-while-revalidate. This file matters because Workers Assets do not run your Next.js headers configuration for static files; without it every asset request pays a revalidation round trip.

Two trade-offs are worth knowing before you start. First, image optimization: the default sharp-based optimizer cannot run in the Workers runtime, so we set images.unoptimized to true and serve images as-is from Cloudinary and Unsplash with explicit sizes — a custom loader is on the roadmap. Second, the compatibility date matters: we pin the newest documented runtime checkpoint our wrangler version supports, because nodejs_compat semantics shift between dates and the smart placement feature measures against them.

The deployment pipeline closes the loop. GitHub Actions installs with bun from the lockfile, applies the idempotent D1 migrations and seeds into the local build environment so prerendering has data, runs opennextjs-cloudflare build, then deploys with the same command. Rollback is a wrangler command away, and because the whole platform is one Worker plus bindings, there is exactly one artifact to roll back.

Our verdict after running this in production: for a content-driven Next.js site, this is the most operationally boring architecture we have ever maintained — and boring is the highest compliment infrastructure can earn.

Topics:#Next.js#Cloudflare#OpenNext#Workers#ISR#Caching
IE

Authored by Intactic Engineering Team

Platform Engineering

Senior engineering leader at Intactic specializing in high-performance cloud architectures, enterprise AI integration, and mission-critical systems.

Related Architecture Teardowns

More strategic insights from our engineering editorial team.