From 5572aa5b6b455724b05e56c6bf974bbb0de38c5b Mon Sep 17 00:00:00 2001 From: Milind Kumar Sahu <41789140+milindkusahu@users.noreply.github.com> Date: Wed, 5 Aug 2026 01:23:26 +0530 Subject: [PATCH 01/15] Fix typo in performance tips section --- pages/cloudflare/perf.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/cloudflare/perf.mdx b/pages/cloudflare/perf.mdx index 89ec921..7b481da 100644 --- a/pages/cloudflare/perf.mdx +++ b/pages/cloudflare/perf.mdx @@ -2,7 +2,7 @@ import { Callout } from "nextra/components"; ## Performance tips -They are a lot of features and knobs you can tune to improve the performance of your app. This page lists some of the recommend settings. +There are a lot of features and knobs you can tune to improve the performance of your app. This page lists some of the recommend settings. ### General From 79441b92bae9befb5f39d5dec4fb521b277c75fd Mon Sep 17 00:00:00 2001 From: Piyush Sharma <90610178+ScienHAC@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:10:42 +0530 Subject: [PATCH 02/15] Mark Node Middleware as supported in Cloudflare docs Updates the Cloudflare documentation checklist to mark Node Middleware as supported. References PR #1279 and #1309 from @opennextjs/cloudflare. --- pages/cloudflare/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/cloudflare/index.mdx b/pages/cloudflare/index.mdx index 3c93a3a..841a916 100644 --- a/pages/cloudflare/index.mdx +++ b/pages/cloudflare/index.mdx @@ -40,7 +40,7 @@ To help improve compatibility, we encourage you to [report bugs](https://github. - [x] [Static Site Generation (SSG)](https://nextjs.org/docs/app/building-your-application/rendering/server-components#static-rendering-default) - [x] [Server-Side Rendering (SSR)](https://nextjs.org/docs/app/building-your-application/rendering/server-components) - [x] [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) -- [ ] [Node Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) introduced in 15.2 are not yet supported +- [x] [Node Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) - [x] [Image optimization](https://nextjs.org/docs/app/building-your-application/optimizing/images) (See [this guide](/cloudflare/howtos/image) to configure [Cloudflare Images](https://developers.cloudflare.com/images/)) - [x] [Partial Prerendering (PPR)](https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering) - [x] [Pages Router](https://nextjs.org/docs/pages) From 89859040a0226ce07cdd1fa4a0a9e98760617de4 Mon Sep 17 00:00:00 2001 From: Piyush Sharma <90610178+ScienHAC@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:15:56 +0530 Subject: [PATCH 03/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- pages/cloudflare/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/cloudflare/index.mdx b/pages/cloudflare/index.mdx index 841a916..62cd3e3 100644 --- a/pages/cloudflare/index.mdx +++ b/pages/cloudflare/index.mdx @@ -40,7 +40,7 @@ To help improve compatibility, we encourage you to [report bugs](https://github. - [x] [Static Site Generation (SSG)](https://nextjs.org/docs/app/building-your-application/rendering/server-components#static-rendering-default) - [x] [Server-Side Rendering (SSR)](https://nextjs.org/docs/app/building-your-application/rendering/server-components) - [x] [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) -- [x] [Node Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) + - [x] [Node Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) - [x] [Image optimization](https://nextjs.org/docs/app/building-your-application/optimizing/images) (See [this guide](/cloudflare/howtos/image) to configure [Cloudflare Images](https://developers.cloudflare.com/images/)) - [x] [Partial Prerendering (PPR)](https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering) - [x] [Pages Router](https://nextjs.org/docs/pages) From 799beef0f05a25f79f55537110d6fc078c6df4ea Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 13 Nov 2026 10:31:14 +0100 Subject: [PATCH 04/15] fixup! Mark Node Middleware as supported in Cloudflare docs --- pages/cloudflare/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/cloudflare/index.mdx b/pages/cloudflare/index.mdx index 62cd3e3..c21fcdf 100644 --- a/pages/cloudflare/index.mdx +++ b/pages/cloudflare/index.mdx @@ -40,7 +40,7 @@ To help improve compatibility, we encourage you to [report bugs](https://github. - [x] [Static Site Generation (SSG)](https://nextjs.org/docs/app/building-your-application/rendering/server-components#static-rendering-default) - [x] [Server-Side Rendering (SSR)](https://nextjs.org/docs/app/building-your-application/rendering/server-components) - [x] [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) - - [x] [Node Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) + - [x] [Node Middleware](https://nextjs.org/docs/app/api-reference/file-conventions/proxy) (i.e. `proxy.ts` in Next.js 16) - experimental support since `@opennextjs/cloudflare` 1.20.3 - [x] [Image optimization](https://nextjs.org/docs/app/building-your-application/optimizing/images) (See [this guide](/cloudflare/howtos/image) to configure [Cloudflare Images](https://developers.cloudflare.com/images/)) - [x] [Partial Prerendering (PPR)](https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering) - [x] [Pages Router](https://nextjs.org/docs/pages) From 8d63167f7b791e6f9c64a07be377f02692623810 Mon Sep 17 00:00:00 2001 From: Mateusz Grab Date: Fri, 6 Feb 2026 18:54:45 +0100 Subject: [PATCH 05/15] Update documentation to reflect approach for R2 upload --- pages/cloudflare/cli.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/cloudflare/cli.mdx b/pages/cloudflare/cli.mdx index 22b7301..6e7768e 100644 --- a/pages/cloudflare/cli.mdx +++ b/pages/cloudflare/cli.mdx @@ -24,11 +24,11 @@ It populates the configured [Open Next cache components](/cloudflare/caching) so -From version 1.13.0 of the Cloudflare adapter, R2 batch uploads are supported out of the box for preview and deploy without any additional setup. +From version 1.13.0 of the Cloudflare adapter, R2 batch uploads are supported out of the box for preview and deploy without any additional setup. However this approach uses under the hood `wrangler r2 bulk put` command, which in the end can be affected by [API token rate limits](https://developers.cloudflare.com/fundamentals/api/reference/limits/). -Before version 1.13.0 of the Cloudflare adapter, the `populateCache` command was supporting R2 batch uploads via `rclone` which required the following additional setup: +For websites that need to bypass API token rate limits, Cloudflare adapter's `populateCache` command (before version 1.13.0 and since version 1.16.4) is supporting R2 batch uploads via `rclone` which requires the following additional setup: -The `populateCache` command supports R2 batching to speed up the upload of large number of files. To enable R2 batching, you need to create an R2 Account API token as described [in the docs](https://developers.cloudflare.com/r2/api/tokens/) and provide the following environment variables: +You need to create an R2 Account API token as described [in the docs](https://developers.cloudflare.com/r2/api/tokens/) and provide the following environment variables: - `R2_ACCESS_KEY_ID`: The access key ID of the R2 API token - `R2_SECRET_ACCESS_KEY`: The secret access key of the R2 API token From e4de3be2009a582e6167214e693bbb107a83dd0d Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 13 Nov 2026 10:31:50 +0100 Subject: [PATCH 06/15] fixup! Update documentation to reflect approach for R2 upload --- pages/cloudflare/cli.mdx | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/pages/cloudflare/cli.mdx b/pages/cloudflare/cli.mdx index 6e7768e..c08d329 100644 --- a/pages/cloudflare/cli.mdx +++ b/pages/cloudflare/cli.mdx @@ -24,17 +24,37 @@ It populates the configured [Open Next cache components](/cloudflare/caching) so -From version 1.13.0 of the Cloudflare adapter, R2 batch uploads are supported out of the box for preview and deploy without any additional setup. However this approach uses under the hood `wrangler r2 bulk put` command, which in the end can be affected by [API token rate limits](https://developers.cloudflare.com/fundamentals/api/reference/limits/). +From version 1.13.0 of the Cloudflare adapter, R2 batch uploads are supported out of the box for preview and deploy without any additional setup. -For websites that need to bypass API token rate limits, Cloudflare adapter's `populateCache` command (before version 1.13.0 and since version 1.16.4) is supporting R2 batch uploads via `rclone` which requires the following additional setup: + -You need to create an R2 Account API token as described [in the docs](https://developers.cloudflare.com/r2/api/tokens/) and provide the following environment variables: +#### Populating R2 with `rclone` -- `R2_ACCESS_KEY_ID`: The access key ID of the R2 API token -- `R2_SECRET_ACCESS_KEY`: The secret access key of the R2 API token -- `CLOUDFLARE_ACCOUNT_ID`: The [account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/#copy-your-account-id) where the R2 bucket is located +From version 1.20.0 of the Cloudflare adapter, you can opt in to upload the R2 incremental cache with [`rclone`](https://rclone.org/) instead of the default upload, which can be faster for a large number of files, by passing `--rclone` to `populateCache remote`, `deploy` or `upload`. The option is only used when populating a remote R2 cache. - +It requires some additional setup: + +1. Install the optional `rclone.js` peer dependency in your project. pnpm users must also allow its install script: + + ```sh + pnpm add rclone.js + pnpm approve-builds # select rclone.js + pnpm rebuild rclone.js + ``` + +2. Create an R2 Account API token as described [in the docs](https://developers.cloudflare.com/r2/api/tokens/) and provide the following environment variables (i.e. in a `.env` file or in your CI environment): + + - `R2_ACCESS_KEY_ID`: The access key ID of the R2 API token + - `R2_SECRET_ACCESS_KEY`: The secret access key of the R2 API token + - `CF_ACCOUNT_ID`: The [account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/#copy-your-account-id) where the R2 bucket is located + +3. Pass the `--rclone` option: + + ```sh + opennextjs-cloudflare deploy --rclone + ``` + +`rclone` may not be supported on all platforms. #### Populating remote bindings when Workers are protected by Cloudflare Access From 1adb28c3d067cf4bde21b8fe1debe28776d5b8de Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 2 Mar 2026 14:05:56 +0100 Subject: [PATCH 07/15] docs(cloudflare): add DO worker setup for skew protection --- pages/cloudflare/howtos/skew.mdx | 96 ++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/pages/cloudflare/howtos/skew.mdx b/pages/cloudflare/howtos/skew.mdx index 36c5f16..7337f21 100644 --- a/pages/cloudflare/howtos/skew.mdx +++ b/pages/cloudflare/howtos/skew.mdx @@ -80,6 +80,102 @@ const nextConfig: NextConfig = { }; ``` +### Using Durable Objects with skew protection + +Cloudflare does not allow a worker with `preview_urls: true` to export Durable Object classes. The solution is to move all DO classes into a separate DO worker and reference them from the main worker using `script_name` bindings. The main worker continues to use `opennextjs-cloudflare deploy` and skew protection as normal. + +**DO worker** + +Create a separate entry point that re-exports the DO classes from the OpenNext build output: + +```js +// do-worker.js +export { DOQueueHandler } from "./.open-next/.build/durable-objects/queue.js"; +export { DOShardedTagCache } from "./.open-next/.build/durable-objects/sharded-tag-cache.js"; +export { BucketCachePurge } from "./.open-next/.build/durable-objects/bucket-cache-purge.js"; + +export default { + async fetch() { + return new Response(null, { status: 404 }); + }, +}; +``` + +**Wrangler configurations** + +The main worker references the DO classes via `script_name`. The DO worker holds the implementations and calls back to the main worker via `WORKER_SELF_REFERENCE` for ISR revalidation: + +```jsonc +// wrangler.jsonc (main worker) +{ + "name": "my-app", + "preview_urls": true, + "services": [ + { "binding": "WORKER_SELF_REFERENCE", "service": "my-app" }, + ], + "durable_objects": { + "bindings": [ + { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache", "script_name": "my-app-do" }, + { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler", "script_name": "my-app-do" }, + { "name": "NEXT_CACHE_DO_PURGE", "class_name": "BucketCachePurge", "script_name": "my-app-do" }, + ], + }, + // ... +} +``` + +```jsonc +// wrangler.do.jsonc (DO worker) +{ + "main": "do-worker.js", + "name": "my-app-do", + "services": [ + { "binding": "WORKER_SELF_REFERENCE", "service": "my-app" }, + ], + "durable_objects": { + "bindings": [ + { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache" }, + { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler" }, + { "name": "NEXT_CACHE_DO_PURGE", "class_name": "BucketCachePurge" }, + ], + }, + "migrations": [ + { "tag": "v1", "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "BucketCachePurge"] }, + ], +} +``` + +**First deployment: circular dependency** + +On a new environment neither worker exists yet. Cloudflare validates `script_name` references and service bindings at deploy time, so deploying either worker first will fail. A bootstrap config for the DO worker without `WORKER_SELF_REFERENCE` breaks the deadlock: + +```jsonc +// wrangler.do.bootstrap.jsonc +{ + "main": "do-worker.js", + "name": "my-app-do", + // no services section + "durable_objects": { + "bindings": [ + { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache" }, + { "name": "NEXT_CACHE_DO_QUEUE", "class_name": "DOQueueHandler" }, + { "name": "NEXT_CACHE_DO_PURGE", "class_name": "BucketCachePurge" }, + ], + }, + "migrations": [ + { "tag": "v1", "new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "BucketCachePurge"] }, + ], +} +``` + +Deploy sequence for a new environment: + +1. `wrangler deploy --config wrangler.do.bootstrap.jsonc` - deploys the DO worker without `WORKER_SELF_REFERENCE` +2. `opennextjs-cloudflare deploy` - deploys the main worker (DO worker now exists, `script_name` resolves) +3. `wrangler deploy --config wrangler.do.jsonc` - redeploys the DO worker with `WORKER_SELF_REFERENCE` + +`DOQueueHandler` handles a missing `WORKER_SELF_REFERENCE` gracefully, so ISR revalidation is only briefly unavailable between steps 1 and 2. Subsequent deployments to an existing environment can skip step 1. + ### What you should know - Because the Worker is configured to run in front of the assets Worker (`run_worker_first`), requesting an asset will count as a request to your Worker From e9ec941e2043916d5294a5c3cab980e5b114bb03 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 13 Nov 2026 10:32:13 +0100 Subject: [PATCH 08/15] fixup! docs(cloudflare): add DO worker setup for skew protection --- pages/cloudflare/howtos/skew.mdx | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/pages/cloudflare/howtos/skew.mdx b/pages/cloudflare/howtos/skew.mdx index 7337f21..3241f80 100644 --- a/pages/cloudflare/howtos/skew.mdx +++ b/pages/cloudflare/howtos/skew.mdx @@ -88,8 +88,7 @@ Cloudflare does not allow a worker with `preview_urls: true` to export Durable O Create a separate entry point that re-exports the DO classes from the OpenNext build output: -```js -// do-worker.js +```js filename="do-worker.js" export { DOQueueHandler } from "./.open-next/.build/durable-objects/queue.js"; export { DOShardedTagCache } from "./.open-next/.build/durable-objects/sharded-tag-cache.js"; export { BucketCachePurge } from "./.open-next/.build/durable-objects/bucket-cache-purge.js"; @@ -105,14 +104,11 @@ export default { The main worker references the DO classes via `script_name`. The DO worker holds the implementations and calls back to the main worker via `WORKER_SELF_REFERENCE` for ISR revalidation: -```jsonc -// wrangler.jsonc (main worker) +```jsonc filename="wrangler.jsonc" { "name": "my-app", "preview_urls": true, - "services": [ - { "binding": "WORKER_SELF_REFERENCE", "service": "my-app" }, - ], + "services": [{ "binding": "WORKER_SELF_REFERENCE", "service": "my-app" }], "durable_objects": { "bindings": [ { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache", "script_name": "my-app-do" }, @@ -124,14 +120,11 @@ The main worker references the DO classes via `script_name`. The DO worker holds } ``` -```jsonc -// wrangler.do.jsonc (DO worker) +```jsonc filename="wrangler.do.jsonc" { "main": "do-worker.js", "name": "my-app-do", - "services": [ - { "binding": "WORKER_SELF_REFERENCE", "service": "my-app" }, - ], + "services": [{ "binding": "WORKER_SELF_REFERENCE", "service": "my-app" }], "durable_objects": { "bindings": [ { "name": "NEXT_TAG_CACHE_DO_SHARDED", "class_name": "DOShardedTagCache" }, @@ -149,8 +142,7 @@ The main worker references the DO classes via `script_name`. The DO worker holds On a new environment neither worker exists yet. Cloudflare validates `script_name` references and service bindings at deploy time, so deploying either worker first will fail. A bootstrap config for the DO worker without `WORKER_SELF_REFERENCE` breaks the deadlock: -```jsonc -// wrangler.do.bootstrap.jsonc +```jsonc filename="wrangler.do.bootstrap.jsonc" { "main": "do-worker.js", "name": "my-app-do", @@ -170,11 +162,12 @@ On a new environment neither worker exists yet. Cloudflare validates `script_nam Deploy sequence for a new environment: -1. `wrangler deploy --config wrangler.do.bootstrap.jsonc` - deploys the DO worker without `WORKER_SELF_REFERENCE` -2. `opennextjs-cloudflare deploy` - deploys the main worker (DO worker now exists, `script_name` resolves) -3. `wrangler deploy --config wrangler.do.jsonc` - redeploys the DO worker with `WORKER_SELF_REFERENCE` +1. `opennextjs-cloudflare build` - builds the app, including the DO classes imported by `do-worker.js` +2. `wrangler deploy --config wrangler.do.bootstrap.jsonc` - deploys the DO worker without `WORKER_SELF_REFERENCE` +3. `opennextjs-cloudflare deploy` - deploys the main worker (DO worker now exists, `script_name` resolves) +4. `wrangler deploy --config wrangler.do.jsonc` - redeploys the DO worker with `WORKER_SELF_REFERENCE` -`DOQueueHandler` handles a missing `WORKER_SELF_REFERENCE` gracefully, so ISR revalidation is only briefly unavailable between steps 1 and 2. Subsequent deployments to an existing environment can skip step 1. +`DOQueueHandler` handles a missing `WORKER_SELF_REFERENCE` gracefully, so ISR revalidation is only briefly unavailable between steps 3 and 4. Subsequent deployments to an existing environment can skip step 2. The DO worker should be deployed after each build so that it uses the DO classes of the same version of the adapter. ### What you should know From ac22f445dfd038c1798ac24f7ab702098ab0b65d Mon Sep 17 00:00:00 2001 From: munahartanto Date: Fri, 6 Nov 2026 16:03:41 +0700 Subject: [PATCH 09/15] docs: note that getCloudflareContext() in a page/layout requires dynamic rendering Calling getCloudflareContext() in a page or layout (not a route handler) is only safe when the route renders dynamically. If Next.js can statically prerender the route, next build will try to do so and fail, or the context will reflect build time rather than the incoming request. --- pages/cloudflare/bindings.mdx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pages/cloudflare/bindings.mdx b/pages/cloudflare/bindings.mdx index 5fd42db..ceb1eb9 100644 --- a/pages/cloudflare/bindings.mdx +++ b/pages/cloudflare/bindings.mdx @@ -35,6 +35,24 @@ export async function GET(request) { + + In a page or layout (as opposed to a route handler), `getCloudflareContext()` can only be used if the route renders dynamically. If Next.js is able to statically prerender the route, `next build` will try to do so and fail (or the context will reflect build time rather than the incoming request). + + Force the route to render dynamically by awaiting one of Next.js's [Dynamic APIs](https://nextjs.org/docs/app/api-reference/functions/headers) before calling `getCloudflareContext()`: + ```js + import { headers } from "next/headers"; + import { getCloudflareContext } from "@opennextjs/cloudflare"; + + export default async function Page() { + await headers(); // forces dynamic rendering + const myKv = getCloudflareContext().env.MY_KV_NAMESPACE; + // ... + } + ``` + + Alternatively, export `export const dynamic = "force-dynamic";` from the page or layout. + + #### How to add bindings to your Worker Add bindings to your Worker by adding them to your [wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). From 48a84e0439bd4ba84c15df56c7f64f5ef7d302c8 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 13 Nov 2026 10:32:47 +0100 Subject: [PATCH 10/15] fixup! docs: note that getCloudflareContext() in a page/layout requires dynamic rendering --- pages/cloudflare/bindings.mdx | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/pages/cloudflare/bindings.mdx b/pages/cloudflare/bindings.mdx index ceb1eb9..0729384 100644 --- a/pages/cloudflare/bindings.mdx +++ b/pages/cloudflare/bindings.mdx @@ -36,21 +36,23 @@ export async function GET(request) { - In a page or layout (as opposed to a route handler), `getCloudflareContext()` can only be used if the route renders dynamically. If Next.js is able to statically prerender the route, `next build` will try to do so and fail (or the context will reflect build time rather than the incoming request). + Calling `getCloudflareContext()` in sync mode in a route that Next.js statically prerenders (i.e. a page or layout that does not use any Next.js Dynamic API) makes `next build` fail. In async mode, the build succeeds but the route is generated with the values available at build time, not the ones of the incoming request. - Force the route to render dynamically by awaiting one of Next.js's [Dynamic APIs](https://nextjs.org/docs/app/api-reference/functions/headers) before calling `getCloudflareContext()`: - ```js - import { headers } from "next/headers"; - import { getCloudflareContext } from "@opennextjs/cloudflare"; - - export default async function Page() { - await headers(); // forces dynamic rendering - const myKv = getCloudflareContext().env.MY_KV_NAMESPACE; - // ... - } - ``` +If the route needs the context of the incoming request, make it render dynamically, i.e. by awaiting one of the Next.js Dynamic APIs such as [`headers()`](https://nextjs.org/docs/app/api-reference/functions/headers) before calling `getCloudflareContext()`: + +```js filename="app/page.js" +import { headers } from "next/headers"; +import { getCloudflareContext } from "@opennextjs/cloudflare"; + +export default async function Page() { + await headers(); // forces dynamic rendering + const myKv = getCloudflareContext().env.MY_KV_NAMESPACE; + // ... +} +``` + +Alternatively, add [`export const dynamic = "force-dynamic";`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config) to the page or layout. - Alternatively, export `export const dynamic = "force-dynamic";` from the page or layout. #### How to add bindings to your Worker From 01053885ee817ef01708bb9901cd2f9c58285479 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 13 Nov 2026 10:32:55 +0100 Subject: [PATCH 11/15] docs(cloudflare): update the supported Next.js versions --- pages/cloudflare/index.mdx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pages/cloudflare/index.mdx b/pages/cloudflare/index.mdx index c21fcdf..ba4331f 100644 --- a/pages/cloudflare/index.mdx +++ b/pages/cloudflare/index.mdx @@ -27,8 +27,11 @@ This is an important difference from `@cloudflare/next-on-pages`, which only sup ### Supported Next.js versions -All minor and patch versions of Next.js 16 and the latest minors of Next.js 14 and 15 are supported. -Next.js 14 support will be dropped Q1 2026. It is [no more supported](https://nextjs.org/support-policy#supported-versions) by the Next.js team. +All minor and patch versions of Next.js 16 and the latest minor of Next.js 15 are supported. + +The minimum versions are raised when Next.js releases security fixes. Check the `next` peer dependency of `@opennextjs/cloudflare` for the exact range supported by your version of the adapter. + +Next.js 14 is no longer supported, it has reached its [end of life](https://nextjs.org/support-policy#supported-versions). To help improve compatibility, we encourage you to [report bugs](https://github.com/opennextjs/opennextjs-cloudflare/issues) and contribute code! From 0e41b4d82e4374efad6c115cff81bf74bd0669a9 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 13 Nov 2026 10:33:15 +0100 Subject: [PATCH 12/15] docs(cloudflare): document the assets configuration required with run_worker_first --- pages/cloudflare/howtos/assets.mdx | 6 ++++++ pages/cloudflare/howtos/skew.mdx | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pages/cloudflare/howtos/assets.mdx b/pages/cloudflare/howtos/assets.mdx index 4fd0151..815bb69 100644 --- a/pages/cloudflare/howtos/assets.mdx +++ b/pages/cloudflare/howtos/assets.mdx @@ -42,6 +42,10 @@ When `run_worker_first` is set to `true`, all the requests will reach the Worker "directory": ".open-next/assets", "binding": "ASSETS", "run_worker_first": true, + // Serve public files at their exact path only, like Next.js + "html_handling": "none", + // "404-page" is also allowed + "not_found_handling": "none", }, // ... } @@ -49,6 +53,8 @@ When `run_worker_first` is set to `true`, all the requests will reach the Worker The Open Next asset resolver will be used to retrieve the assets from the Worker. +Since `@opennextjs/cloudflare` 1.20.16, public files are only served at their exact path when the Worker runs first, like Next.js. The build fails when `run_worker_first` is enabled (`true` or a list with at least one non-negated pattern) unless `html_handling` is set to `"none"` and `not_found_handling` is set to `"none"` or `"404-page"`. Other values would serve the files at other paths (i.e. `/about` for `public/about.html`), which a middleware matching the exact path of the file would not cover. + When `run_worker_first=true`, assets are served behind the middleware and Next rewrites and headers from [the Next config](https://nextjs.org/docs/app/api-reference/config/next-config-js). The [headers](https://developers.cloudflare.com/workers/static-assets/headers/) and [redirects](https://developers.cloudflare.com/workers/static-assets/redirects/) configured for the Worker Static Assets do not apply in this case. `run_worker_first=true` should be used if you plan to use skew protection. diff --git a/pages/cloudflare/howtos/skew.mdx b/pages/cloudflare/howtos/skew.mdx index 3241f80..a4faa1a 100644 --- a/pages/cloudflare/howtos/skew.mdx +++ b/pages/cloudflare/howtos/skew.mdx @@ -35,7 +35,7 @@ export default { **Wrangler configuration** -The Worker needs to serve the correct version of the app assets. For that it need be be executed before incoming requests are matched against the assets. Set [`run_worker_first`](https://developers.cloudflare.com/workers/static-assets/binding/#run_worker_first) to `true` in your wrangler configuration to enable this behavior: +The Worker needs to serve the correct version of the app assets. For that it need be be executed before incoming requests are matched against the assets. Set [`run_worker_first`](https://developers.cloudflare.com/workers/static-assets/binding/#run_worker_first) to `true` in your wrangler configuration to enable this behavior. `html_handling` and `not_found_handling` must then be set as below (see [Workers Static Assets](/cloudflare/howtos/assets#run_worker_firsttrue)): ```jsonc filename="wrangler.jsonc" { @@ -45,6 +45,10 @@ The Worker needs to serve the correct version of the app assets. For that it nee "directory": ".open-next/assets", "binding": "ASSETS", "run_worker_first": true, + // Serve public files at their exact path only, like Next.js + "html_handling": "none", + // "404-page" is also allowed + "not_found_handling": "none", }, // ... } From ed650b970fdd2df44d1e4f85fc1e0c40ca41ba1f Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 13 Nov 2026 10:33:28 +0100 Subject: [PATCH 13/15] docs(cloudflare): the workerd build is used for every package not bundled by Next.js --- pages/cloudflare/howtos/workerd.mdx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pages/cloudflare/howtos/workerd.mdx b/pages/cloudflare/howtos/workerd.mdx index 35aeb15..ad3b130 100644 --- a/pages/cloudflare/howtos/workerd.mdx +++ b/pages/cloudflare/howtos/workerd.mdx @@ -20,9 +20,10 @@ there are still minor differences. Some packages publish code for different runt With such exports, Node.js applications use either `src/index.js` or `cjs/src/index.js` depending if the app use ESM or CJS. However we want to use the `workerd` specific entrypoint when using the Cloudflare adapter. -For that, you need to instruct Next.js not to bundle packages as it would use the node conditions by default. -To do that, add those packages in the `serverExternalPackages` key of your `next.config.ts`: +The adapter uses the `workerd` build of the packages that Next.js does not bundle. Since `@opennextjs/cloudflare` 1.20.15, this applies to all of them (and to their dependencies): the packages listed in `serverExternalPackages` and the packages that Next.js [keeps external by default](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) (i.e. `pg` or `@prisma/client`). Before 1.20.15, only the packages listed in `serverExternalPackages` were using their `workerd` build. + +Next.js bundles the other packages with the Node.js conditions. For those, you need to instruct Next.js not to bundle them by adding them to the `serverExternalPackages` key of your `next.config.ts`: ```ts filename="next.config.ts" import type { NextConfig } from "next"; From b25e04e580094f0d619d117346485516d1de19ca Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 13 Nov 2026 10:33:48 +0100 Subject: [PATCH 14/15] docs(cloudflare): document the cache purge behavior changes and BucketCachePurgeBase --- pages/cloudflare/caching.mdx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pages/cloudflare/caching.mdx b/pages/cloudflare/caching.mdx index 06191da..a2ef251 100644 --- a/pages/cloudflare/caching.mdx +++ b/pages/cloudflare/caching.mdx @@ -692,7 +692,12 @@ You can only enable cache purge functionality on a zone (e.g., when using a cust The cache purge component automatically clears the cache when a page is revalidated. It is only necessary if you use On-Demand revalidation along with one of the cache components that leverage the Cache API. -This component can either call the Cache API's purge function directly or route the purge request through an intermediate durable object. Using a durable object helps buffer requests and avoid reaching [API rate limits](https://developers.cloudflare.com/cache/how-to/purge-cache/#hostname-tag-prefix-url-and-purge-everything-limits). +This component can either call the Cache API's purge function directly (`purgeCache({ type: "direct" })`, the default) or route the purge request through an intermediate durable object (`purgeCache({ type: "durableObject" })`). Using a durable object helps buffer requests and avoid reaching [API rate limits](https://developers.cloudflare.com/cache/how-to/purge-cache/#hostname-tag-prefix-url-and-purge-everything-limits). + +Since `@opennextjs/cloudflare` 1.20.15, the `type` is honored for every purge: + +- With `durableObject`, the `NEXT_CACHE_DO_PURGE` binding is required. When it is missing, an error is logged and the cache is not purged. The adapter no longer falls back to direct purges, which are not buffered and count against the account-wide rate limit. +- When `CACHE_PURGE_API_TOKEN` or `CACHE_PURGE_ZONE_ID` is missing, the tag cache is still updated but the purge is reported as failed (`Cloudflare cache purge failed: missing-credentials`). Set both variables, or remove `cachePurge` from your config if you don't need to purge the cache. Cache purge are only called when you call `revalidateTag`, `revalidatePath` or `res.revalidate` in the pages router. It is not called for ISR revalidation. @@ -746,6 +751,8 @@ If you want to use the durable object option, you will need to add the following You can customize the duration of the cache purge buffering with the `NEXT_CACHE_DO_PURGE_BUFFER_TIME_IN_SECONDS` environment variable. The default is 5 seconds. It works by buffering the purge requests for a given amount of time and then sending them all at once. This is useful to avoid hitting the API rate limits. +If you need a different behavior (i.e. a different storage schema or purging several zones), you can implement your own durable object by extending `BucketCachePurgeBase` from `@opennextjs/cloudflare/durable-objects/bucket-cache-purge` (available since 1.20.15). It keeps the alarm scheduling and the queue draining and lets you override the storage, batching, execution and settlement hooks. See the [`BucketCachePurge` implementation](https://github.com/opennextjs/opennextjs-cloudflare/blob/main/packages/cloudflare/src/api/durable-objects/bucket-cache-purge.ts) for an example. + ### Debugging You can add `NEXT_PRIVATE_DEBUG_CACHE=1` to your app `.env` file to debug any cache issue. From 7590c46e70223aac7f9843a15392d39067d98917 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 13 Nov 2026 11:36:30 +0100 Subject: [PATCH 15/15] fixup! docs(cloudflare): add DO worker setup for skew protection --- pages/cloudflare/howtos/skew.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pages/cloudflare/howtos/skew.mdx b/pages/cloudflare/howtos/skew.mdx index a4faa1a..c83553b 100644 --- a/pages/cloudflare/howtos/skew.mdx +++ b/pages/cloudflare/howtos/skew.mdx @@ -128,6 +128,8 @@ The main worker references the DO classes via `script_name`. The DO worker holds { "main": "do-worker.js", "name": "my-app-do", + // The DO worker is only accessed through bindings + "workers_dev": false, "services": [{ "binding": "WORKER_SELF_REFERENCE", "service": "my-app" }], "durable_objects": { "bindings": [ @@ -150,6 +152,8 @@ On a new environment neither worker exists yet. Cloudflare validates `script_nam { "main": "do-worker.js", "name": "my-app-do", + // The DO worker is only accessed through bindings + "workers_dev": false, // no services section "durable_objects": { "bindings": [