Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .changeset/fix-load-manifest-graceful-fallback.md
Original file line number Diff line number Diff line change
@@ -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.
22 changes: 22 additions & 0 deletions packages/cloudflare/src/cli/build/patches/plugins/load-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Loading