diff --git a/.changeset/fix-load-manifest-graceful-fallback.md b/.changeset/fix-load-manifest-graceful-fallback.md new file mode 100644 index 000000000..5627f1092 --- /dev/null +++ b/.changeset/fix-load-manifest-graceful-fallback.md @@ -0,0 +1,27 @@ +--- +"@opennextjs/cloudflare": patch +--- + +fix: handle known optional manifests gracefully in loadManifest/evalManifest patches + +Next.js loads certain manifests with `handleMissing: true` (returning `{}` when the file doesn't +exist). The adapter's build-time glob scan doesn't find these files when they're conditionally +generated, so the patched function threw at runtime, crashing dynamic routes with 500. + +Instead of a blanket catch-all, handle only the specific optional manifests from Next.js +`route-module.ts`: + +- `react-loadable-manifest` (Turbopack per-route, not all routes have dynamic imports) +- `subresource-integrity-manifest` (only when `experimental.sri` configured) +- `server-reference-manifest` (App Router only) +- `dynamic-css-manifest` (Pages Router + Webpack only) +- `fallback-build-manifest` (only for `/_error` page) +- `prefetch-hints` (new in Next.js 16.2) +- `_client-reference-manifest.js` (optional for static metadata routes, evalManifest) + +Manifest matching strips `.json` before comparison since some Next.js constants omit +the extension (`SUBRESOURCE_INTEGRITY_MANIFEST`, `DYNAMIC_CSS_MANIFEST`, etc.). + +Unknown manifests still throw to surface genuine errors. + +Fixes #1141. diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-manifest.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-manifest.ts index 347288d04..e94973e4b 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-manifest.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-manifest.ts @@ -66,6 +66,23 @@ function loadManifest($PATH, $$$ARGS) { return process.env.NEXT_BUILD_ID; } ${returnManifests} + // Known optional manifests \u2014 Next.js loads these with handleMissing: true + // (see vercel/next.js packages/next/src/server/route-modules/route-module.ts). + // Return {} to match Next.js behaviour instead of crashing the worker. + // Note: Some manifest constants in Next.js omit the .json extension + // (e.g. SUBRESOURCE_INTEGRITY_MANIFEST, DYNAMIC_CSS_MANIFEST), so we + // strip .json before matching to handle both forms. + { + const p = $PATH.replace(/\\.json$/, ""); + if (p.endsWith("react-loadable-manifest") || + p.endsWith("subresource-integrity-manifest") || + p.endsWith("server-reference-manifest") || + p.endsWith("dynamic-css-manifest") || + p.endsWith("fallback-build-manifest") || + p.endsWith("prefetch-hints")) { + return {}; + } + } throw new Error(\`Unexpected loadManifest(\${$PATH}) call!\`); }`, } satisfies RuleConfig; @@ -110,6 +127,11 @@ function evalManifest($PATH, $$$ARGS) { function evalManifest($PATH, $$$ARGS) { $PATH = $PATH.replaceAll(${JSON.stringify(sep)}, ${JSON.stringify(posix.sep)}); ${returnManifests} + // client-reference-manifest is optional for static metadata routes + // (see vercel/next.js route-module.ts, loaded with handleMissing: true) + if ($PATH.endsWith("_client-reference-manifest.js")) { + return { __RSC_MANIFEST: {} }; + } throw new Error(\`Unexpected evalManifest(\${$PATH}) call!\`); }`, } satisfies RuleConfig;