Repository navigation
Description
Link to the code that reproduces this issue
To Reproduce
git clone https://github.com/davidbarratt/nextjs-nft-hoisted-symlink-repro
cd nextjs-nft-hoisted-symlink-repro
pnpm install
pnpm build
find .next -xtype lfind prints one dangling symlink:
.next/standalone/node_modules/.pnpm/node_modules/scheduler
-> ../[email protected]/node_modules/scheduler
node check-nft.mjs inspects the *.nft.json traces directly — the same defect
before output: "standalone" materializes it:
alias traced, zero target files: scheduler -> .../.pnpm/[email protected]/node_modules/scheduler
hoisted aliases traced: 47
aliases with no traced target files: 1
The app is one route handler that imports @react-pdf/renderer, plus a layout.
Current vs. Expected behavior
Current: a route's *.nft.json lists pnpm's hoisted-store link
node_modules/.pnpm/node_modules/scheduler as a bare entry, and traces no files
from the copy that link resolves to. Two copies of scheduler are in the store —
0.28.0 from react-dom (what the hoisted link points at) and
0.25.0-rc-603e6108-20241029 from @react-pdf/reconciler (the only one whose
files are traced). The alias is therefore packed empty.
Deploying that output to Vercel fails during extraction, before the app runs:
Building: Extracting deployment files...
Error: ENOENT: no such file or directory, realpath
'/vercel/path0/node_modules/.pnpm/node_modules/scheduler'
Expected: an alias listed in a trace is packed with the files it resolves to,
or is not listed at all. Either way the output contains no symlink without a
target.
Provide environment information
Operating System:
Platform: linux
Arch: arm64
Binaries:
Node: 24.20.0
pnpm: 11.11.0
Relevant Packages:
next: 16.4.0-canary.32 (also reproduced on 16.3.5)
react: 19.3.0
react-dom: 19.3.0
typescript: 5.9.3
Next.js Config:
output: standaloneWhich area(s) are affected? (Select all that apply)
Turbopack, Output (export/standalone), Module Resolution
Which stage(s) are affected? (Select all that apply)
next build (local), Vercel (Deployed)
Additional context
Impact. Every deploy of an affected app fails at extraction — there is no
partial or degraded mode, and no retry clears it. I first hit this across ten CI
runs on unrelated branches of a private monorepo, on three separate apps, each
blocked until the output was patched; a second app failed identically on
supports-color, which has four copies in its store. [citation needed] — that
CI is private, so the public evidence for the failure is the reproduction above.
What is and isn't required, each verified by rebuilding with only that
changed:
| Changed | Result |
|---|---|
output: "standalone" removed |
still 1 bad alias (trace only; find has no tree to scan) |
serverExternalPackages removed |
still 1 bad alias |
both removed — empty next.config.mjs |
still 1 bad alias |
overrides: { scheduler: 0.28.0 } |
0 bad aliases |
So neither config option is implicated. The last row is the mechanism: once the
hoisted link and the traced files agree on a version, the defect is gone.
I mention serverExternalPackages only because the app where I first saw this
listed @react-pdf/renderer there, and
#95816 points at externalized
packages — but removing it here changes nothing, so it is not the trigger.
pnpm's default symlinked linker is required. node-linker=hoisted has no
.pnpm/node_modules directory and cannot hit this.
Workarounds, in preference order:
- Dedupe the package so the alias and traced files agree (the table's last row).
Only viable when one version actually satisfies both dependents. - Delete targetless links from the build output before uploading —
find .vercel/output -xtype l -delete. A link with no target cannot serve a
request, so removing it is safe, but it is a patch over the output rather than
a fix.
Relationship to
#95816. Same shape — an alias
traced without its store target files — and possibly the same machinery, but a
different alias class and failure point, so I filed separately rather than
commenting there:
| #95816 | this | |
|---|---|---|
| Alias | Turbopack content-hashed .next/node_modules/<pkg>-<hash> |
pnpm hoisted store node_modules/.pnpm/node_modules/<pkg> |
| Package manager | bun isolated linker | pnpm |
| Surfaces at | runtime, ERR_MODULE_NOT_FOUND |
deploy, ENOENT on realpath |
| Trigger | shared SSR chunk referencing a package the endpoint does not import | two copies in the store; the hoisted link points at the untraced one |
This reproduction emits no .next/node_modules/<pkg>-<hash> entries at all, so
it is not the #95816 path. Happy to fold this in as a comment there if you read
them as one bug.
Open questions I could not answer:
- I have not bisected the first canary that introduced this. It reproduces on
16.3.5and16.4.0-canary.32; I have not tested the 16.2.x line, where
#95816 reports its own variant absent. - In the private app this began failing partway through a day on which neither
the Next version nor the lockfile changed — the same commit range built clean
earlier and dangling later. I could not identify what shifted, so I cannot say
whether emission here is deterministic or depends on chunking.
Activity
Tiancheng-Xu commentedon Sep 17, 2026
I can investigate this tracing path. I will reproduce the dangling pnpm hoisted-store alias with the linked fixture, trace how the target package files are selected, and add a focused regression test with the smallest fix if confirmed. I will report the validation results in the PR.
vpontis commentedon Sep 17, 2026
Same failure with Bun's isolated linker, so this is not pnpm-specific.
Every Vercel deploy of three Next.js apps in our monorepo started failing at 21:45 UTC on Sep 16 with no dependency or config change on our side:
Bun hoists
escape-string-regexp@4(eslint's) intonode_modules/.bun/node_modules/, while the code path actually traced uses@5throughmdast-util-find-and-replace. The page's.nft.jsonlists the hoisted link without tracing its target, so the upload has the link and none of the@4files. Two more packages of the same shape (mime-db,mime-types) surfaced one at a time after the first was excluded.Workaround for now:
outputFileTracingExcludesfor the dangling links. The timing across unrelated projects suggests Vercel's extraction step started realpath-ing every traced entry, and this tracer gap has been there for a while.fix(lycoris): flat install is the whole fix; drop the trace include
bgw commentedon Sep 21, 2026
Hi everyone. Sorry about the disruption.
It appears that this corresponds to an internal incident on Vercel involving changes to symlink resolution that affected some number of deployments. The change rolled out on September 16th and was rolled back later that day.
It's possible that #98798 is still valid regardless of the Vercel issue, I'll take a look at that PR and triage it separately.
roji-tech commentedon Sep 23, 2026
A third, independent confirmation of the same duplicate-package mechanism, from an unrelated production monorepo (Next.js 16.2.6, Turbopack, pnpm 11) — and the same trigger package.
We've had a long-standing, unexplained Vercel deploy failure at the "Extracting deployment files..." step (opaque, no diagnostic text via CLI/dashboard/
vercel inspect --logs), on routes that traced@react-pdf/rendererthroughoutputFileTracingIncludes. Two fixes suggested by Vercel's own support tooling were each tested against a realvercel deploy --prebuiltand disproven — including switching tonode-linker=hoisted, which flattened the tree with zero broken symlinks by our ownlstatverification, yet the deploy still failed with the byte-for-byte identical opaque signature.Having now found this issue, we confirmed the same duplicate-version shape in our own pnpm store:
Same trigger package (
@react-pdf/renderer→@react-pdf/reconcilerpinning its ownscheduler) as the original report here. We haven't yet applied/tested thepnpm.overridesdedupe workaround against a real deploy, but the mechanism match is exact enough that it seemed worth adding as a third data point, especially since the fix (#98798) is still open/unmerged — this doesn't look like it was only the Sep 16 Vercel-side incident.github-actions commentedon Oct 7, 2026
This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.