Nextjs
Source:
src/Cloudflare/Website/Nextjs.ts
A Cloudflare Worker deployed from a Next.js project.
Nextjs builds the app with the wrangler-free OpenNext pipeline from
@distilled.cloud/nextjs:
next build runs through @opennextjs/cloudflare, the resulting worker
is bundled into a self-contained ES module set, and the static assets
(including prerendered pages and the read-only incremental cache) deploy
as Workers static assets. Input files are content-hashed so unchanged
projects skip the build and deploy entirely.
Both @distilled.cloud/nextjs and its peer @opennextjs/cloudflare
must be installed in the deploying project — the source provider is
loaded with a dynamic import().
Local dev (alchemy dev) defaults to preview parity — the built worker
served under workerd. Set nextjs: { devMode: "hmr" } for the real
next dev (Turbopack HMR) with the Worker’s bindings proxied onto
getCloudflareContext().
ISR comes in two flavors, chosen by the project’s open-next.config.ts:
the zero-infra static-assets incremental cache (prerendered pages serve
as built; revalidation writes are a no-op), or the fully writable
KV-backed setup (revalidatePath/revalidateTag and time-based
regeneration all work) — see the Writable ISR section below. OpenNext’s
WORKER_SELF_REFERENCE self service binding is always wired on deploy.
Known limitations (upstream @opennextjs/cloudflare):
- Edge-runtime routes/pages (
export const runtime = "edge") are not supported — the build fails with the offending route list; remove the directive (the node runtime runs on Workers). Middleware is fine. next/imageoptimization requires a zone with Cloudflare Images; onworkers.dev, useunoptimized(images serve as raw assets).- Partial Prerendering /
"use cache"(cacheComponents) and Pages-Routeri18nconfig are untested/out of scope for now. App Router i18n via middleware works (middleware is fully supported).
Deploying a Next.js App
Section titled “Deploying a Next.js App”A single call builds the app with OpenNext and deploys the worker plus
its static assets. The project needs an open-next.config.ts — the
read-only static-assets incremental cache is a good default:
import { defineCloudflareConfig } from "@opennextjs/cloudflare";import staticAssetsIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/static-assets-incremental-cache";
export default defineCloudflareConfig({ incrementalCache: staticAssetsIncrementalCache,});Basic Next.js site
const site = yield* Cloudflare.Website.Nextjs("Site");Explicit project root
const site = yield* Cloudflare.Website.Nextjs("Site", { rootDir: "./apps/web",});Bindings
Section titled “Bindings”Resources passed via env become Worker bindings, readable in route
handlers and server components through OpenNext’s
getCloudflareContext().
const bucket = yield* Cloudflare.R2.Bucket("Uploads");const site = yield* Cloudflare.Website.Nextjs("Site", { env: { UPLOADS: bucket, },});import { getCloudflareContext } from "@opennextjs/cloudflare";
export async function PUT(request: Request) { const { env } = getCloudflareContext(); await env.UPLOADS.put("key", await request.text()); return Response.json({ ok: true });}Writable ISR
Section titled “Writable ISR”With the KV incremental cache, ISR revalidation actually writes:
revalidatePath / revalidateTag purge entries, and time-based
revalidate windows regenerate pages in the background through the
same-worker Durable Object queue. Configure OpenNext for it and bind
the pieces — WORKER_SELF_REFERENCE is wired automatically:
import { defineCloudflareConfig } from "@opennextjs/cloudflare";import kvIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/kv-incremental-cache";import doQueue from "@opennextjs/cloudflare/overrides/queue/do-queue";import kvNextTagCache from "@opennextjs/cloudflare/overrides/tag-cache/kv-next-tag-cache";
export default defineCloudflareConfig({ incrementalCache: kvIncrementalCache, queue: doQueue, tagCache: kvNextTagCache,});const incCache = yield* Cloudflare.KV.Namespace("NextIncCache");const tagCache = yield* Cloudflare.KV.Namespace("NextTagCache");
const site = yield* Cloudflare.Website.Nextjs("Site", { env: { NEXT_INC_CACHE_KV: incCache, NEXT_TAG_CACHE_KV: tagCache, // The revalidation queue: a Durable Object class shipped in the // OpenNext worker bundle itself. NEXT_CACHE_DO_QUEUE: Cloudflare.DurableObject("NEXT_CACHE_DO_QUEUE", { className: "DOQueueHandler", }), },});Custom Rebuild Scope
Section titled “Custom Rebuild Scope”By default, every project file outside build outputs is hashed to decide
whether a rebuild is needed. Use memo to narrow the scope when the
project has large directories that don’t affect the build output.
const site = yield* Cloudflare.Website.Nextjs("Site", { memo: { include: ["app/**", "public/**", "package.json", "next.config.mjs", "open-next.config.ts"], },});Build Configuration
Section titled “Build Configuration”The nextjs prop tunes the OpenNext pipeline: a custom build command,
minification, or reusing an existing .next build.
const site = yield* Cloudflare.Website.Nextjs("Site", { nextjs: { buildCommand: "npx next build --no-lint", minify: true, },});Class Form
Section titled “Class Form”Calling Nextjs with no arguments returns a constructor you can
extend to declare the Worker as a named class. The class is both an
Effect you can yield* to deploy and a type you can reference
elsewhere — useful when other resources need to bind to this Worker.
class Site extends Cloudflare.Website.Nextjs<Site>()("Site", { rootDir: "./apps/web",}) {}
const site = yield* Site;