Skip to content
5 changes: 5 additions & 0 deletions .changeset/rude-sloths-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@opennextjs/cloudflare": patch
---

add(patch): Patch getIncrementalCache in route-module.ts for ISR support in Next.js 15.4"
4 changes: 2 additions & 2 deletions examples/e2e/app-router/e2e/og.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { expect, test } from "@playwright/test";
import { validateMd5 } from "../../utils";

// This is the md5sums of the expected PNGs generated with `md5sum <file>`
const OG_MD5 = "6e5e794ac0c27598a331690f96f05d00";
const API_OG_MD5 = "cac95fc3e2d4d52870c0536bb18ba85b";
const OG_MD5 = "83cfda4e78b037aa3d9ab465292550ef";
const API_OG_MD5 = "6a22b4ff74e0dd8c377e2640dafe3e40";

test("Open-graph image to be in metatags and present", async ({ page, request }) => {
await page.goto("/og");
Expand Down
6 changes: 1 addition & 5 deletions examples/e2e/experimental/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ const nextConfig: NextConfig = {
ignoreBuildErrors: true,
},
experimental: {
ppr: "incremental",
// Node middleware is not supported yet in cloudflare
// See https://github.com/opennextjs/opennextjs-cloudflare/issues/617
// nodeMiddleware: true,
dynamicIO: true,
cacheComponents: true,
},
};

Expand Down
2 changes: 1 addition & 1 deletion examples/e2e/experimental/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"dependencies": {
"@opennextjs/cloudflare": "workspace:*",
"next": "15.4.0-canary.14",
"next": "15.4.2-canary.29",
"react": "catalog:e2e",
"react-dom": "catalog:e2e"
},
Expand Down
2 changes: 1 addition & 1 deletion examples/playground15/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"cf-typegen": "wrangler types --env-interface CloudflareEnv"
},
"dependencies": {
"next": "^15.3.5",
"next": "^15.4.5",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
Expand Down
9 changes: 0 additions & 9 deletions packages/cloudflare/src/cli/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,4 @@ function ensureNextjsVersionSupported(options: buildHelper.BuildOptions) {
logger.error("Next.js version unsupported, please upgrade to version 14.2 or greater.");
process.exit(1);
}
// TODO: remove when 15.4 is supported
// Note: `e2e/experimental` is on 15.4.0-canary.14 which works
if (
!options.appPath.endsWith("opennextjs-cloudflare/examples/e2e/experimental") &&
buildHelper.compareSemver(options.nextVersion, ">=", "15.4.0")
) {
logger.error("Next.js version unsupported, the latest supported version is 15.3");
process.exit(1);
}
}
4 changes: 4 additions & 0 deletions packages/cloudflare/src/cli/build/bundle-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { patchPagesRouterContext } from "./patches/plugins/pages-router-context.
import { patchDepdDeprecations } from "./patches/plugins/patch-depd-deprecations.js";
import { fixRequire } from "./patches/plugins/require.js";
import { shimRequireHook } from "./patches/plugins/require-hook.js";
import { patchRouteModules } from "./patches/plugins/route-module.js";
import { setWranglerExternal } from "./patches/plugins/wrangler-external.js";
import { copyPackageCliFiles, needsExperimentalReact, normalizePath } from "./utils/index.js";

Expand Down Expand Up @@ -103,6 +104,7 @@ export async function bundleServer(buildOpts: BuildOptions, projectOpts: Project
inlineFindDir(updater, buildOpts),
inlineLoadManifest(updater, buildOpts),
patchNextServer(updater, buildOpts),
patchRouteModules(updater, buildOpts),
patchDepdDeprecations(updater),
patchResolveCache(updater, buildOpts),
// Apply updater updates, must be the last plugin
Expand Down Expand Up @@ -147,6 +149,8 @@ export async function bundleServer(buildOpts: BuildOptions, projectOpts: Project
"process.env.TURBOPACK": "false",
// This define should be safe to use for Next 14.2+, earlier versions (13.5 and less) will cause trouble
"process.env.__NEXT_EXPERIMENTAL_REACT": `${needsExperimentalReact(nextConfig)}`,
// Fix `res.validate` in Next 15.4 (together with the `route-module` patch)
"process.env.__NEXT_TRUST_HOST_HEADER": "true",
},
banner: {
// We need to import them here, assigning them to `globalThis` does not work because node:timers use `globalThis` and thus create an infinite loop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ function loadManifest($PATH, $$$ARGS) {
fix: `
function loadManifest($PATH, $$$ARGS) {
$PATH = $PATH.replaceAll(${JSON.stringify(sep)}, ${JSON.stringify(posix.sep)});
if ($PATH === "/.next/BUILD_ID") {
return process.env.NEXT_BUILD_ID;
if ($PATH.endsWith(".next/BUILD_ID")) {
return process.env.NEXT_BUILD_ID;
}
${returnManifests}
throw new Error(\`Unexpected loadManifest(\${$PATH}) call!\`);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { describe, expect, test } from "vitest";

import { computePatchDiff } from "../../utils/test-patch.js";
import { buildIdRule, createCacheHandlerRule, createComposableCacheHandlersRule } from "./next-server.js";
import {
buildIdRule,
createCacheHandlerRule,
createComposableCacheHandlersRule,
disableNodeMiddlewareRule,
} from "./next-server.js";

describe("Next Server", () => {
const nextServerCode = `
Expand Down Expand Up @@ -85,6 +90,22 @@ class NextNodeServer extends _baseserver.default {
pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
};
}
async loadNodeMiddleware() {
if (!process.env.NEXT_MINIMAL) {
try {
var _functionsConfig_functions;
const functionsConfig = this.renderOpts.dev ? {} : require((0, _path.join)(this.distDir, 'server', _constants.FUNCTIONS_CONFIG_MANIFEST));
if (this.renderOpts.dev || (functionsConfig == null ? void 0 : (_functionsConfig_functions = functionsConfig.functions) == null ? void 0 : _functionsConfig_functions['/_middleware'])) {
// if used with top level await, this will be a promise
return require((0, _path.join)(this.distDir, 'server', 'middleware.js'));
}
} catch (err) {
if ((0, _iserror.default)(err) && err.code !== 'ENOENT' && err.code !== 'MODULE_NOT_FOUND') {
throw err;
}
}
}
}
// ...
}`;

Expand Down Expand Up @@ -187,4 +208,46 @@ class NextNodeServer extends _baseserver.default {
"
`);
});

test("disable node middleware", () => {
expect(computePatchDiff("next-server.js", nextServerCode, disableNodeMiddlewareRule))
.toMatchInlineSnapshot(`
"Index: next-server.js
===================================================================
--- next-server.js
+++ next-server.js
@@ -1,5 +1,4 @@
-
class NextNodeServer extends _baseserver.default {
constructor(options){
// Initialize super class
super(options);
@@ -79,21 +78,8 @@
pages: (0, _findpagesdir.findDir)(dir, "pages") ? true : false
};
}
async loadNodeMiddleware() {
- if (!process.env.NEXT_MINIMAL) {
- try {
- var _functionsConfig_functions;
- const functionsConfig = this.renderOpts.dev ? {} : require((0, _path.join)(this.distDir, 'server', _constants.FUNCTIONS_CONFIG_MANIFEST));
- if (this.renderOpts.dev || (functionsConfig == null ? void 0 : (_functionsConfig_functions = functionsConfig.functions) == null ? void 0 : _functionsConfig_functions['/_middleware'])) {
- // if used with top level await, this will be a promise
- return require((0, _path.join)(this.distDir, 'server', 'middleware.js'));
- }
- } catch (err) {
- if ((0, _iserror.default)(err) && err.code !== 'ENOENT' && err.code !== 'MODULE_NOT_FOUND') {
- throw err;
- }
- }
- }
- }
+ // patched by open next
+}
// ...
}
\\ No newline at end of file
"
`);
});
});
16 changes: 16 additions & 0 deletions packages/cloudflare/src/cli/build/patches/plugins/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,28 @@ export function patchNextServer(updater: ContentUpdater, buildOpts: BuildOptions
"composable-cache.cjs"
);
contents = patchCode(contents, createComposableCacheHandlersRule(composableCacheHandler));

// Node middleware are not supported on Cloudflare yet
contents = patchCode(contents, disableNodeMiddlewareRule);

return contents;
},
},
]);
}

// Do not try to load Node middlewares
export const disableNodeMiddlewareRule = `
rule:
pattern:
selector: method_definition
context: "class { async loadNodeMiddleware($$$PARAMS) { $$$_ } }"
fix: |-
async loadNodeMiddleware($$$PARAMS) {
// patched by open next
}
`;

export const buildIdRule = `
rule:
pattern:
Expand Down
Loading