vercel / vercel/nft

Conditional subpath with only "import" condition mis-traced — wrong file copied on Vercel deploy

Open
#599 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

invalid
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
  1. React Router v7 (framework mode) app deployed via @vercel/react-router preset.
  2. SSR module statically imports loadCoreSdkScript from @paypal/paypal-js/sdk-v6. Vite externalizes the package, so the SSR bundle literally contains import { loadCoreSdkScript } from \"@paypal/paypal-js/sdk-v6\".
  3. 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.1 in 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.1 preset
  • 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.