From 346d5fdd1611ff2e601de3627486397e75906730 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 24 Aug 2026 16:17:43 +0200 Subject: [PATCH] fix(patches): include preview-props.json in loadManifest glob for Next.js 16.4 Next.js 16.4 moved preview props out of prerender-manifest.json into a standalone .next/server/preview-props.json, loaded unconditionally by NextNodeServer.getPreviewProps(). Its name doesn't end in -manifest, so the glob in load-manifest.ts missed it and every dynamic route 500d. Co-Authored-By: Claude Opus 5 (1M context) --- .changeset/fix-preview-props-manifest.md | 14 ++++++++++++++ .../src/cli/build/patches/plugins/load-manifest.ts | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-preview-props-manifest.md diff --git a/.changeset/fix-preview-props-manifest.md b/.changeset/fix-preview-props-manifest.md new file mode 100644 index 000000000..e21db1012 --- /dev/null +++ b/.changeset/fix-preview-props-manifest.md @@ -0,0 +1,14 @@ +--- +"@opennextjs/cloudflare": patch +--- + +fix(patches): include `preview-props.json` in loadManifest build-time inlining + +Next.js 16.4 moved preview props out of `prerender-manifest.json` into a standalone +`.next/server/preview-props.json` (`PREVIEW_PROPS_MANIFEST`), loaded unconditionally by +`NextNodeServer.getPreviewProps()` from the `Server` constructor. The file exists in the build +output but wasn't matched by the glob pattern `*-manifest.json`, so the patched `loadManifest()` +threw at runtime and every dynamic route returned a 500. + +It is inlined rather than falling back to `{}` because it holds the actual draft-mode +id and signing/encryption keys. 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 143dd544f..fa8907346 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-manifest.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-manifest.ts @@ -39,8 +39,13 @@ async function getLoadManifestRule(buildOpts: BuildOptions) { const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); const dotNextDir = join(baseDir, ".next"); + // Most server manifests are named `*-manifest.json`, but a few are not: + // `required-server-files.json`, `prefetch-hints.json` (Next 16.2+) and + // `server/preview-props.json` (Next 16.4+, previously inlined in the + // prerender manifest). They are loaded unconditionally by `NextNodeServer`, + // so they must be inlined rather than falling back to `{}`. const manifests = await glob( - join(dotNextDir, "**/{*-manifest,required-server-files,prefetch-hints}.json"), + join(dotNextDir, "**/{*-manifest,required-server-files,prefetch-hints,preview-props}.json"), { windowsPathsNoEscape: true, }