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/:
- Root
package.json — no "type" field → defaults to commonjs.
dist/esm/package.json — present, but only sets sideEffects and imports:
{ "sideEffects": false, "imports": { ... } }
No "type": "module".
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)
- Emit
dist/esm/package.json with "type": "module" alongside the existing sideEffects / imports fields.
- 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.
Summary
@clerk/nextjs(7.0.7 through latest 7.2.5) shipsdist/esm/*.jswith ESM syntax and extensionless relative imports, but the package has no"type": "module"metadata — neither on the rootpackage.jsonnor ondist/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"cannotimport { clerkClient } from "@clerk/nextjs/server"vianode/tsx. Node.js itself (vianext dev/next build) andvitestaren'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.tsNode compatibility
Tested against
@clerk/nextjs@7.2.5+tsx@4.20.6, consumerpackage.jsonhas"type": "module":clerkClient typeof: functionSyntaxError: The requested module '@clerk/nextjs/server' does not provide an export named 'clerkClient'SyntaxErrorRunning the same repro with
"type": "commonjs"(thenpm initdefault) masks the failure on all three —tsxfalls 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/:package.json— no"type"field → defaults tocommonjs.dist/esm/package.json— present, but only setssideEffectsandimports:{ "sideEffects": false, "imports": { ... } }"type": "module".dist/esm/server/index.js— uses ESM syntax with extensionless relative imports: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 withdoes not provide an export named 'clerkClient'.This is exactly the
unexpected-module-syntaxrule thatpublintand@arethetypeswrong/cliflag. The rule is currently suppressed in this repo's own CI:Proposed fix (tsup config)
dist/esm/package.jsonwith"type": "module"alongside the existingsideEffects/importsfields.--ignore-rules unexpected-module-syntaxfromlint:attwonce the output shape is clean;publintshould 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.7applying only the"type": "module"addition todist/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 failingtsxworkload.So while an earlier version of this issue suggested a second step to rewrite imports with
.jsextensions, that's not actually required for the loader to accept the package. A one-line tsup change that writesdist/esm/package.jsonwith"type": "module"appears to be sufficient.Impact
"type": "module"cannot runtsx/nodescripts that transitively import@clerk/nextjs/serveron Node 22+. This covers operational tools, backfill scripts, ad-hoc CLI scripts, and anything else outside the Next runtime.Environment
@clerk/nextjs: 7.0.7 (originally observed), also reproduced on latest 7.2.5tsx: 4.20.6package.json"type":"module"Happy to provide additional diagnostics — let me know.