Deploy Next.js to Cloudflare Workers via the OpenNext adapter (@opennextjs/cloudflare). Use for SSR/ISR/SSG/App or Pages Router, getCloudflareContext, bindings (D1/R2/KV/AI/Hyperdrive), caching tiers, skew protection, multi-worker, custom worker, env vars, or worker size/runtime/keep_names/FinalizationRegistry/connection-scoping errors.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Deploy Next.js to Cloudflare Workers via the OpenNext adapter (@opennextjs/cloudflare). Use for SSR/ISR/SSG/App or Pages Router, getCloudflareContext, bindings (D1/R2/KV/AI/Hyperdrive), caching tiers, skew protection, multi-worker, custom worker, env vars, or worker size/runtime/keep_names/FinalizationRegistry/connection-scoping errors.
Deploy Next.js applications to Cloudflare Workers using the OpenNext adapter (@opennextjs/cloudflare). The adapter takes a standard Next.js build, runs package.json build script, then transforms the output to run on the Workers runtime using the Node.js compatibility layer (nodejs_compat) — not the Edge runtime.
Critical Requirements (get these wrong and the build/runtime fails)
Requirement
Value
Why
Runtime
Node.js (default). Remove every export const runtime = "edge";
Edge runtime is unsupported; OpenNext uses nodejs_compat.
Older dates break FinalizationRegistry, DOs, and more.
Wrangler
≥ 3.99.0 to deploy; ≥ 4.13.0 for keep_names; ≥ 4.36.0 for stable remote bindings
Feature gates in the docs.
Next.js
v16 all minors/patches supported; latest minors of v14 and v15; v14 dropped Q1 2026
Stated on the overview page.
Worker size (gzip)
3 MiB Free / 10 MiB Paid (compressed only)
Hard Cloudflare limits.
Windows: not fully guaranteed (Next.js tooling issues). Use WSL, a Linux VM, or Linux/macOS CI. See known issue #1305.
Disambiguation: this skill vs nextjs
nextjs skill → framework/App Router/Server Components/Cache Components patterns, any platform (Vercel, self-hosted, ...). Use for async params, migration, .
proxy.ts
"use cache"
THIS skill (cloudflare-nextjs) → deploying Next.js to Workers via the OpenNext adapter: wrangler.jsonc, open-next.config.ts, getCloudflareContext, caching tiers, bindings, skew protection, multi-worker, the Workers-specific errors.
proxy.ts caveat (Next 16): Next 16 renamed middleware.ts → proxy.ts, but @opennextjs/cloudflare does not recognize proxy.ts yet (issue #1277) — on Cloudflare, keep using middleware.ts. This is the one place the nextjs skill's guidance does NOT apply here.
C3 scaffolds a Next.js app, installs @opennextjs/cloudflare, creates wrangler.jsonc + open-next.config.ts + .dev.vars, wires package.json scripts, and (if R2 is enabled) creates an R2 bucket for caching.
Existing Next.js project (one command)
npx @opennextjs/cloudflare migrate
migrate automates: install adapter + wrangler, create wrangler.jsonc/open-next.config.ts/.dev.vars, update scripts, add public/_headers, add .open-next to .gitignore, wire initOpenNextCloudflareForDev() into next.config.ts, and create+configure an R2 cache bucket (only if R2 is enabled on the account).
Then create the three files (see references/wrangler.jsonc, references/open-next.config.ts, references/package.json) and add the dev/preview/deploy/upload/cf-typegen scripts. Pin adapter versions and audit before upgrading — see the dependency-upgrade skill.
The four scripts
// package.json{"dev":"next dev",// fast HMR via Next dev server"preview":"opennextjs-cloudflare build && opennextjs-cloudflare preview",// build + run in workerd locally"deploy":"opennextjs-cloudflare build && opennextjs-cloudflare deploy",// build + serve immediately"upload":"opennextjs-cloudflare build && opennextjs-cloudflare upload",// build + upload a version (gradual rollout)"cf-typegen":"wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"}
dev — fastest feedback loop; add initOpenNextCloudflareForDev() to next.config.ts so getCloudflareContext() works locally with simulated/remote bindings.
preview — runs in the actual Workers runtime (not Node). Always run before deploy to catch runtime-only issues.
deploy — populates the remote cache, then wrangler deploy. App serves immediately.
upload — populates remote cache, then wrangler versions upload. Does NOT serve automatically; for gradual deployments.
build, preview, deploy, upload all implicitly call populateCache — you do not need to run it manually.
TypeScript types:npm run cf-typegen generates cloudflare-env.d.ts (re-run after any binding change).
Remote bindings (local dev → real resources): stabilized in Wrangler 4.36.0. On older wrangler, enable via initOpenNextCloudflareForDev({ experimental: { remoteBindings: true } }) and use the experimental_remote (not remote) key on binding options. Note: remote bindings are also used during build.
Full patterns (D1/R2/KV/AI/Hyperdrive, Drizzle, Prisma, Stripe) → references/bindings-and-services.md.
Caching — three components, three tiers
OpenNext's cache has three parts: Incremental Cache (storage), Queue (dedupe/revalidate), Tag Cache (on-demand revalidateTag/revalidatePath).
Reserved binding names (do not reuse): ASSETS, WORKER_SELF_REFERENCE, NEXT_INC_CACHE_R2_BUCKET, NEXT_CACHE_DO_QUEUE, NEXT_TAG_CACHE_D1, NEXT_TAG_CACHE_DO_SHARDED, NEXT_CACHE_DO_PURGE, IMAGES.
Avoid Workers KV for incremental cache — eventually consistent, can persist stale data indefinitely.
Cache interception + PPR: incompatible today; cache interception is NOT enabled by default and does not work with PPR.
On-demand revalidation requires both a Tag Cache and the Cache Purge component (cache purge only works on a zone/custom domain; needs CACHE_PURGE_API_TOKEN + CACHE_PURGE_ZONE_ID secrets).
Pages Routerres.revalidate requires a self-reference service binding named WORKER_SELF_REFERENCE.
Headers caveat: the Worker does not run in front of static assets, so next.config.tsheaders() for public/ and immutable build files do not apply. Use public/_headers.
Deep dive (all options, env vars, regional modes, migration from 0.6) → references/caching.md and references/known-issues.md.
Common Integrations (condensed — full patterns in references)
Drizzle + D1/Hyperdrive/PG, Prisma + D1/PG/Hyperdrive — request-scoped clients via cache() from react; maxUses: 1 on PG pools; getCloudflareContext({ async: true }) for ISR/SSG; Prisma needs previewFeatures = ["driverAdapters"], no output dir in schema.prisma, and serverExternalPackages: ["@prisma/client", ".prisma/client"]. → references/bindings-and-services.md
Stripe — Workers have no node:https; pass httpClient: Stripe.createFetchHttpClient(). → references/bindings-and-services.md
Image optimization — images.binding: "IMAGES" in wrangler.jsonc, or a custom loader (/cdn-cgi/image/...) for zones. minimumCacheTTL and dangerouslyAllowLocalIP are not supported; custom loader bypasses middleware and ignores remotePatterns. → references/advanced.md
Env vars — use Next.js .env files (not just .dev.vars); NEXTJS_ENV in .dev.vars selects the env; --keep-vars on deploy; secrets are write-only. → references/dev-deploy-and-env.md
Custom worker (add scheduled, Durable Object exports) — point main at your worker that re-exports the generated fetch handler. → references/advanced.md
Multi-worker (split middleware from server) — reduces per-worker memory + cold starts; incompatible with preview URLs, skew protection, and @opennextjs/cloudflare deploy. → references/advanced.md
Skew protection (preview-URL-based version matching) — cloudflare.skewProtection.enabled, run_worker_first: true, getDeploymentId(), env vars CF_WORKER_NAME/CF_PREVIEW_DOMAIN/CF_WORKERS_SCRIPTS_API_TOKEN/CF_ACCOUNT_ID. Disabled for Workers with a Durable Object (move DOs to a separate worker). → references/advanced.md
Top Errors (full catalog → references/error-catalog-extended.md)
1. Worker size limit exceeded
"Your Worker exceeded the size limit of 3 MiB" (Free) / "10 MiB" (Paid). Only gzip size counts. Free → upgrade to Paid. Paid → analyze bundle: npx @opennextjs/cloudflare build, then inspect .open-next/server-functions/default/handler.mjs.meta.json (visualize with ESBuild Bundle Analyzer); remove unused deps, use dynamic imports.
2. Cannot perform I/O on behalf of a different request
Global DB client (e.g. postgres, pg Pool) reused across requests. Create the client inside the request handler (or use cache() from react), and maxUses: 1 for PG pools.
3. NPM package import / "Could not resolve <package>"
Enable nodejs_compat, ensure compatibility_date ≥ 2024-09-23. Some packages ship a workerd export — add them to serverExternalPackages in next.config.ts (e.g. @prisma/client, .prisma/client, postgres, jose, react-textarea-autosize, @libsql/isomorphic-ws). Or set .env: WRANGLER_BUILD_CONDITIONS="" + WRANGLER_BUILD_PLATFORM="node".
5. Failed to load chunk server/chunks/ssr/<name>.js
Outdated adapter with Turbopack builds. Upgrade @opennextjs/cloudflare to latest, or switch to webpack (next build without --turbo).
6. ReferenceError: FinalizationRegistry is not defined
compatibility_date too old. Set "compatibility_date": "2025-05-05" (or later) in wrangler.jsonc.
7. Uncaught ReferenceError: __name is not defined
Wrangler's esbuild keep-names injects __name into generated script strings that some libs (e.g. next-themes) eval at runtime. Set "keep_names": false in wrangler.jsonc (requires Wrangler ≥ 4.13.0). You lose original function names in debugging.
8. "Failed to send request to R2 worker" / 403 during populateCache remote
Account protected by Cloudflare Access blocks the open-next-cache-populate helper worker. Do not create a separate Access app for it; add a Service Auth policy (Include = Any Access Service Token) to the existing app covering *.<account>.workers.dev, create a service token, and export CLOUDFLARE_ACCESS_CLIENT_ID / CLOUDFLARE_ACCESS_CLIENT_SECRET.
Known Open Bugs (live tracker)
Always check the issue tracker — these are recurring at the time of writing: