Skip to main content

cloudflare-nextjs

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.

Source facts

Repository
secondsky/claude-skills
Last source activity
August 5, 2026 at 16:05
Detected SKILL.md language
English
Stars
240
Forks
33

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
17 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
cloudflare-nextjs
description
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.
license
MIT
metadata
{"version":"2.0.0","last_verified":"2026-08-05T00:00:00.000Z","package_versions":{"@opennextjs/cloudflare":"^1.18.1","next":"^14.2.0 || ^15.0.0 || ^16.0.0","wrangler":"^4.81.0"},"compatibility_requirements":{"compatibility_date":"2025-05-05","compatibility_flags":["nodejs_compat","global_fetch_strictly_public"],"min_wrangler":"3.99.0"},"token_savings":"~60%","errors_prevented":"18+","sources":{"overview":"https://opennext.js.org/cloudflare","get_started":"https://opennext.js.org/cloudflare/get-started","cli":"https://opennext.js.org/cloudflare/cli","bindings":"https://opennext.js.org/cloudflare/bindings","caching":"https://opennext.js.org/cloudflare/caching","howto_stripe":"https://opennext.js.org/cloudflare/howtos/stripeAPI","howto_db":"https://opennext.js.org/cloudflare/howtos/db","howto_dev_deploy":"https://opennext.js.org/cloudflare/howtos/dev-deploy","howto_env_vars":"https://opennext.js.org/cloudflare/howtos/env-vars","howto_image":"https://opennext.js.org/cloudflare/howtos/image","howto_custom_worker":"https://opennext.js.org/cloudflare/howtos/custom-worker","howto_keep_names":"https://opennext.js.org/cloudflare/howtos/keep_names","howto_workerd":"https://opennext.js.org/cloudflare/howtos/workerd","howto_skew":"https://opennext.js.org/cloudflare/howtos/skew","howto_assets":"https://opennext.js.org/cloudflare/howtos/assets","howto_multi_worker":"https://opennext.js.org/cloudflare/howtos/multi-worker","performance":"https://opennext.js.org/cloudflare/perf","known_issues":"https://opennext.js.org/cloudflare/known-issues","troubleshooting":"https://opennext.js.org/cloudflare/troubleshooting","migration_06_to_1":"https://opennext.js.org/cloudflare/migrate-from-0.6-to-1.0.0-beta","open_issues":"https://github.com/opennextjs/opennextjs-cloudflare/issues"},"keywords":["OpenNext Cloudflare","@opennextjs/cloudflare","opennextjs-cloudflare cli","Next.js on Workers","Next.js App Router Cloudflare","Next.js Pages Router Cloudflare","Next.js SSR Cloudflare","Next.js ISR Workers","server components cloudflare","server actions workers","Next.js middleware workers","getCloudflareContext","nodejs_compat","global_fetch_strictly_public","workerd runtime","worker size limit","next.js runtime compatibility","database connection scoping","R2 incremental cache","Durable Objects queue","skew protection","multi-worker","custom worker","keep_names","FinalizationRegistry"]}
# OpenNext Cloudflare Adapter — Next.js on Workers 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`. | | `compatibility_flags` | `["nodejs_compat", "global_fetch_strictly_public"]` | Node APIs + allow `fetch()` in app code. | | `compatibility_date` | **≥ `2024-09-23`**; **≥ `2025-05-05`** recommended (FinalizationRegistry) | 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`, `proxy.ts` migration, `"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. ## Quick Start ### New project (recommended) ```bash npm create cloudflare@latest -- my-next-app --framework=next --platform=workers ``` 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) ```bash 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). <details><summary>Manual install (if you prefer not to run migrate)</summary> ```bash npm install @opennextjs/cloudflare@latest npm install --save-dev wrangler@latest ``` 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. </details> ### The four scripts ```jsonc // 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. ### Dev `next.config.ts` ```ts import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* ... */ }; export default nextConfig; import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"; initOpenNextCloudflareForDev(); ``` ## Accessing Cloudflare Bindings — `getCloudflareContext()` **Do NOT use `process.env` for bindings.** The official API is `getCloudflareContext()` from `@opennextjs/cloudflare`. ```ts import { getCloudflareContext } from "@opennextjs/cloudflare"; export async function GET() { const { env, cf, ctx } = getCloudflareContext(); await env.MY_KV.put("foo", "bar"); return new Response(await env.MY_KV.get("foo")); } ``` **Static routes (ISR/SSG) MUST use async mode** — and be careful: secrets/local values are used during static generation. ```ts const { env } = await getCloudflareContext({ async: true }); ``` **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`). | Site profile | Incremental | Queue | Tag Cache | When | |---|---|---|---|---| | **SSG only** (no revalidation) | `staticAssetsIncrementalCache` + `enableCacheInterception: true` | none | none | Fastest option; read-only | | **Small site** (ISR/on-demand) | `r2IncrementalCache` | `doQueue` | `d1NextTagCache` | Low traffic; D1 tag cache | | **Large/high-traffic site** | `withRegionalCache(r2IncrementalCache, { mode: "long-lived" })` | `doQueue` | `doShardedTagCache({ baseShardSize: 12 })` + `purgeCache({ type: "direct" })` | DO-sharded; add cache purge if using on-demand | **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 Router** `res.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.ts` `headers()` 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"`. ### 4. SSRF (CVE-2025-6087) — versions < 1.3.0 `/_next/image` SSRF. Upgrade immediately: `@opennextjs/cloudflare@^1.3.0` (current: `^1.18.1`). ### 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: | # | Bug | Workaround | |---|---|---| | [#1171](https://github.com/opennextjs/opennextjs-cloudflare/issues/1171) | **v1.18.0 breaks R2 cache population** (pinned) | Pin to 1.17.x or upgrade past the fix |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub