Skip to content

dist/esm lacks 'type: module' metadata — breaks @clerk/nextjs on Node 22+ under type:module consumers #8396

@benfsmith

Description

@benfsmith

Summary

@clerk/nextjs (7.0.7 through latest 7.2.5) ships dist/esm/*.js with ESM syntax and extensionless relative imports, but the package has no "type": "module" metadata — neither on the root package.json nor on dist/esm/package.json. Node's ESM loader (22+) rejects the named-export re-export chain at link time, so any consumer declared as "type": "module" cannot import { clerkClient } from "@clerk/nextjs/server" via node/tsx. Node.js itself (via next dev/next build) and vitest aren't affected because they wrap the loader.

Reproduction

Minimal gist (3 files, under 40 lines total): https://gist.github.com/benfsmith/744d12873ac427a860f10ba39fb47eea

# clone/download gist → npm install → run under three Node versions
npx tsx repro.ts

Node compatibility

Tested against @clerk/nextjs@7.2.5 + tsx@4.20.6, consumer package.json has "type": "module":

Node Result
20.20.1 clerkClient typeof: function
22.22.2 SyntaxError: The requested module '@clerk/nextjs/server' does not provide an export named 'clerkClient'
25.7.0 ❌ same SyntaxError

Running the same repro with "type": "commonjs" (the npm init default) masks the failure on all three — tsx falls back to the CJS interop path that lazily resolves named exports. Which is why a lot of consumers don't hit this until they flip a workspace to "type": "module".

Root cause

Inside node_modules/@clerk/nextjs/:

  1. Root package.json — no "type" field → defaults to commonjs.
  2. dist/esm/package.json — present, but only sets sideEffects and imports:
    { "sideEffects": false, "imports": { ... } }
    No "type": "module".
  3. dist/esm/server/index.js — uses ESM syntax with extensionless relative imports:
    import { createRouteMatcher } from "./routeMatcher";
    import { clerkClient } from "./clerkClient";
    export { auth, buildClerkProps, clerkClient, /* ... */ };

Node 20 tolerates this via syntax-detection + legacy extensionless resolution. Node 22 tightened the loader: without an explicit "type": "module" anywhere, the file is treated as CJS and the named-export re-export chain fails at instantiate-time with does not provide an export named 'clerkClient'.

This is exactly the unexpected-module-syntax rule that publint and @arethetypeswrong/cli flag. The rule is currently suppressed in this repo's own CI:

"lint:attw": "attw --pack . --profile node16 --ignore-rules unexpected-module-syntax"

Proposed fix (tsup config)

  1. Emit dist/esm/package.json with "type": "module" alongside the existing sideEffects / imports fields.
  2. Drop --ignore-rules unexpected-module-syntax from lint:attw once the output shape is clean; publint should then report green.

I'm happy to open a draft PR if that would help — the tsup-side change is small, but you'll know faster than I would whether there are downstream tests relying on the current shape.

Edit — empirical note on step 1 being sufficient

After filing this I tested the fix via pnpm patch @clerk/nextjs@7.0.7 applying only the "type": "module" addition to dist/esm/package.json (no .js-extension rewrite on relative imports). That alone resolves the Node 22 + Node 25 failure — the extensionless relative specifiers still resolve correctly once the package is declared ESM. Verified end-to-end on Node 22.22 and Node 25.9 with the original failing tsx workload.

So while an earlier version of this issue suggested a second step to rewrite imports with .js extensions, that's not actually required for the loader to accept the package. A one-line tsup change that writes dist/esm/package.json with "type": "module" appears to be sufficient.

Impact

  • Any consumer workspace declared "type": "module" cannot run tsx/node scripts that transitively import @clerk/nextjs/server on Node 22+. This covers operational tools, backfill scripts, ad-hoc CLI scripts, and anything else outside the Next runtime.
  • Node 20 is EOL April 2026, so pinning Node 20 as a workaround expires very soon; Node 22+ is effectively the only supported line going forward.
  • Next.js dev/build and Vitest are unaffected (both wrap the loader).

Environment

  • @clerk/nextjs: 7.0.7 (originally observed), also reproduced on latest 7.2.5
  • tsx: 4.20.6
  • OS: macOS 15 (Darwin 25.5.0)
  • Consumer package.json "type": "module"

Happy to provide additional diagnostics — let me know.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions