You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
opennextjs-cloudflare build fails while bundling the server function:
✘ [ERROR] Cannot read directory ".open-next/server-functions/default/node_modules/.pnpm/next@16.2.9_.../node_modules/react-dom": Access is denied.
✘ [ERROR] Could not resolve "styled-jsx"
.open-next/.../next/dist/compiled/next-server/pages.runtime.prod.js: ... require("styled-jsx") ...
Every directory that was a pnpm symlink in the traced files (react, react-dom, styled-jsx, …) is recreated in .open-next as a broken link, so esbuild cannot traverse into it.
Root cause
In dist/build/copyTracedFiles.js, symlinks are recreated with the raw readlinkSync value:
letsymlink=null;try{symlink=readlinkSync(from);}catch(e){/* ignore */}if(symlink){try{symlinkSync(symlink,to);// ← breaks on Windows}catch(e){if(e.code!=="EEXIST")throwe;}}
This has two Windows-specific problems:
Link type falls back to 'file' for not-yet-existing targets. Per the Node docs for fs.symlinkSync(target, path[, type]), when type is omitted on Windows, Node autodetects the target type — and "if the target does not exist, 'file' will be used". Relative symlink targets are resolved against the link's parent directory, and copyTracedFiles copies entries in traversal order, so at the moment a pnpm directory link is recreated, its relative target inside the .open-next mirror frequently does not exist yet. The result is a file-type symlink pointing at a directory, which Windows cannot traverse — readdir fails with EPERM/"Access is denied" even after the target directory is later populated. (Linux has no file/dir link distinction, which is why this never surfaces there.)
Raw relative targets are environment-sensitive. In our reproduction environment, node_modules installed by pnpm 10.x used absolute junctions (raw recreation happens to survive, since the target is absolute and already exists), while pnpm 11.5.2 produced relative symlinks (cf. Use symlink instead of junction even in Windows if possible (=in developer mode) pnpm/symlink-dir#55), which triggers (1). The failure family is not new to pnpm 11, though — opennextjs-cloudflare#414 reports the same copyTracedFiles symlink breakage on pnpm 10.5.0 — so this appears to be a latent issue that surfaces depending on the link style of the installed node_modules.
Note this also interacts with Next's own standalone output: .next/standalone already contains links recreated the same way by Next itself, so from can itself be a broken link — any fix that calls realpathSync(from) unconditionally will hit EPERM there (we verified this).
Proposed fix (verified)
Recreate the link on Windows as a junction whose target is the raw symlink value resolved against the destination's parent directory — semantically identical to what the relative symlink means on Linux (it points into the .open-next mirror structure). Junctions fit this exact case: NTFS junctions can only point to directories (and every pnpm link recreated here is a directory link), they require no admin rights/Developer Mode, they resolve lazily (the link stays valid once the target directory is populated later in the copy), and they never touch the original store. Junction targets must be absolute — Node normalizes 'junction' targets to absolute automatically, but we resolve explicitly for clarity:
Applied as a patch to @opennextjs/aws 4.0.2 across three real Next.js 16 apps on Windows:
2 apps with pnpm nodeLinker: isolated, 1 with nodeLinker: hoisted
All three: clean opennextjs-cloudflare build reaches "Worker saved", no esbuild resolution errors
All three deployed to Cloudflare Workers and serving HTTP 200 in production
Related
[BUG] pnpm symlink compatibility opennextjs-cloudflare#414 — same failure family (EPERM: operation not permitted, copyfile on pnpm symlinks, Windows, reported on pnpm 10.5.0); closed with only the node-linker=hoisted workaround. The fix above solves it without forcing a layout change.
Environment
nodeLinker: isolated, the default) — also reproduced withnodeLinker: hoisted@opennextjs/aws4.0.2 (via@opennextjs/cloudflare1.19.11)next build --webpackSymptom
opennextjs-cloudflare buildfails while bundling the server function:Every directory that was a pnpm symlink in the traced files (
react,react-dom,styled-jsx, …) is recreated in.open-nextas a broken link, so esbuild cannot traverse into it.Root cause
In
dist/build/copyTracedFiles.js, symlinks are recreated with the rawreadlinkSyncvalue:This has two Windows-specific problems:
Link type falls back to
'file'for not-yet-existing targets. Per the Node docs forfs.symlinkSync(target, path[, type]), whentypeis omitted on Windows, Node autodetects the target type — and "if the target does not exist,'file'will be used". Relative symlink targets are resolved against the link's parent directory, andcopyTracedFilescopies entries in traversal order, so at the moment a pnpm directory link is recreated, its relative target inside the.open-nextmirror frequently does not exist yet. The result is a file-type symlink pointing at a directory, which Windows cannot traverse — readdir fails withEPERM/"Access is denied" even after the target directory is later populated. (Linux has no file/dir link distinction, which is why this never surfaces there.)Raw relative targets are environment-sensitive. In our reproduction environment,
node_modulesinstalled by pnpm 10.x used absolute junctions (raw recreation happens to survive, since the target is absolute and already exists), while pnpm 11.5.2 produced relative symlinks (cf. Use symlink instead of junction even in Windows if possible (=in developer mode) pnpm/symlink-dir#55), which triggers (1). The failure family is not new to pnpm 11, though — opennextjs-cloudflare#414 reports the samecopyTracedFilessymlink breakage on pnpm 10.5.0 — so this appears to be a latent issue that surfaces depending on the link style of the installednode_modules.Note this also interacts with Next's own standalone output:
.next/standalonealready contains links recreated the same way by Next itself, sofromcan itself be a broken link — any fix that callsrealpathSync(from)unconditionally will hitEPERMthere (we verified this).Proposed fix (verified)
Recreate the link on Windows as a junction whose target is the raw symlink value resolved against the destination's parent directory — semantically identical to what the relative symlink means on Linux (it points into the
.open-nextmirror structure). Junctions fit this exact case: NTFS junctions can only point to directories (and every pnpm link recreated here is a directory link), they require no admin rights/Developer Mode, they resolve lazily (the link stays valid once the target directory is populated later in the copy), and they never touch the original store. Junction targets must be absolute — Node normalizes'junction'targets to absolute automatically, but we resolve explicitly for clarity:Validation
Applied as a patch to
@opennextjs/aws4.0.2 across three real Next.js 16 apps on Windows:nodeLinker: isolated, 1 withnodeLinker: hoistedopennextjs-cloudflare buildreaches "Worker saved", no esbuild resolution errorsRelated
EPERM: operation not permitted, copyfileon pnpm symlinks, Windows, reported on pnpm 10.5.0); closed with only thenode-linker=hoistedworkaround. The fix above solves it without forcing a layout change.utils.tssymlink handling referenced in thecopyTracedFilescomment).