Conditional subpath with only "import" condition mis-traced — wrong file copied on Vercel deploy
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 1.7k
- Forks
- 185
- PR merge metrics
- No merged PRs in 30d
Description
Summary
When a package's exports field declares a subpath whose only conditions are "types" + "import" (no "default", no "require"), nft appears to mis-trace it during Vercel function packaging. At runtime, Node's ESM loader resolves the bare specifier to a different file than the subpath should yield — specifically, it ends up loading the package's main entry instead of the subpath entry — and module instantiation fails because the expected named export is missing.
Repro
Package: @paypal/paypal-js@10.0.1.
Its exports map:
{
".": {
"types": "./types/index.d.ts",
"import": "./dist/esm/paypal-js.js",
"require": "./dist/cjs/paypal-js.js"
},
"./sdk-v6": {
"types": "./types/v6/index.d.ts",
"import": "./dist/v6/esm/paypal-js.js"
}
}
The ./sdk-v6 subpath has no default/require condition.
dist/v6/esm/paypal-js.js exports loadCoreSdkScript, version.
dist/esm/paypal-js.js (the . entry) exports loadScript, loadCustomScript, version — no loadCoreSdkScript.
What I did
- React Router v7 (framework mode) app deployed via
@vercel/react-routerpreset. - SSR module statically imports
loadCoreSdkScriptfrom@paypal/paypal-js/sdk-v6. Vite externalizes the package, so the SSR bundle literally containsimport { loadCoreSdkScript } from \"@paypal/paypal-js/sdk-v6\". - Deploy to Vercel (Node 24 runtime).
Expected
Function loads, Node resolves @paypal/paypal-js/sdk-v6 to dist/v6/esm/paypal-js.js, named import loadCoreSdkScript binds successfully.
Actual
Function crashes on instantiation:
SyntaxError: The requested module '@paypal/paypal-js/sdk-v6' does not
provide an export named 'loadCoreSdkScript'
at #asyncInstantiate (node:internal/modules/esm/module_job:319:21)
This is Node's named-export validation failing — the resolved module exists and loads, but doesn't have loadCoreSdkScript. The export shape matches the v5 entry (.), strongly suggesting nft caused the deployed function's resolution to fall through to the main entry instead of the ./sdk-v6 subpath.
Local sanity checks (all pass)
node --input-type=module -e \"import('@paypal/paypal-js/sdk-v6').then(m => console.log(Object.keys(m)))\"→['loadCoreSdkScript', 'version']- Fresh
npm i @paypal/paypal-js@10.0.1in an empty dir + ESM import works. - Same on Node 24.
Hypothesis
When nft walks the package and a subpath lacks "default"/"require" conditions, the trace step misses or drops the v6 file (or the package.json is rewritten such that Node falls back to legacy pkg + subpath.js / main resolution). Either way, the deployed function ends up with the wrong file.
Workaround
Convert the static import to a dynamic await import(\"@paypal/paypal-js/sdk-v6\") inside a client-only code path so nft never has to trace the subpath through the SSR graph.
Why this matters
This is a spec-valid exports shape (subpath conditions are optional and "import" alone is legitimate for ESM-only subpaths). Any package can ship this and silently break on Vercel. PayPal's v6 SDK is one example; this will recur as more libraries publish ESM-only subpaths.
Environment
- Vercel Node.js runtime (Node 24)
@vercel/react-router@1.3.1preset- React Router v7 framework mode (Vite SSR)
@paypal/paypal-js@10.0.1
Happy to put together a tighter minimal repro repo if useful — let me know the shape you'd prefer (bare nft CLI invocation vs. full Vercel deploy).
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the static import case with the @paypal/paypal-js package exports map and the @vercel/react-router SSR setup, then trace how nft handles the ./sdk-v6 subpath with only types and import conditions. Compare the traced files and rewritten package metadata with Node's resolution; done means the deployed function resolves ./sdk-v6 to dist/v6/esm/paypal-js.js and binds loadCoreSdkScript successfully.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nodejs
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100