#98791·Next.js

Turbopack traces a pnpm hoisted-store alias without its target files, producing a dangling symlink that fails Vercel deploys with ENOENT

Author: davidbarrattCreated Sep 17, 2026Updated Sep 17, 2026
LabelsOutputTurbopackModule Resolution

Link to the code that reproduces this issue

https://github.com/davidbarratt/nextjs-nft-hoisted-symlink-repro

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 l

find 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: standalone

Which 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:

  1. Dedupe the package so the alias and traced files agree (the table's last row). Only viable when one version actually satisfies both dependents.
  2. 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.5 and 16.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.