Skip to content

fix(nextjs): declare dist/esm as ESM and split ./server by react-server condition#8397

Open
jacekradko wants to merge 5 commits intomainfrom
jacek/nextjs-type-module
Open

fix(nextjs): declare dist/esm as ESM and split ./server by react-server condition#8397
jacekradko wants to merge 5 commits intomainfrom
jacek/nextjs-type-module

Conversation

@jacekradko
Copy link
Copy Markdown
Member

@jacekradko jacekradko commented Apr 24, 2026

Summary

Two changes, landed together to keep Next 15 working while fixing native-Node ESM resolution of @clerk/nextjs/server:

  1. Declare dist/esm as ESM via a type:module sidecar. packages/nextjs/package.esm.json now ships with "type": "module" so Node 22+ and tsx consumers under "type": "module" can import named exports from @clerk/nextjs/server without the SyntaxError: does not provide an export named 'clerkClient' reported in dist/esm lacks 'type: module' metadata — breaks @clerk/nextjs on Node 22+ under type:module consumers #8396.

  2. Split ./server by the react-server condition and import server-only statically. Under ESM classification webpack preserves require('server-only') as a raw Node runtime call, which resolves without the react-server condition and trips the client-only guard — that was the Next 15 prerender regression the sidecar alone introduced. The fix:

    • New entrypoint packages/nextjs/src/server/index.rsc.ts selected by the react-server export condition. It re-exports the pages-safe surface plus auth and currentUser.
    • packages/nextjs/src/server/index.ts keeps the pages-safe surface (getAuth, buildClerkProps, clerkMiddleware, etc.). Pages-router consumers never transitively load auth.ts.
    • auth.ts and currentUser.ts now import 'server-only' at the top of the file instead of calling require('server-only') lazily inside the function body. Analyzable by webpack in both CJS and ESM module classifications.

lint:attw swaps unexpected-module-syntax for --ignore-rules false-cjs: with type: module set, unexpected-module-syntax stops firing, but attw now flags false-cjs on every subpath because type declarations are still emitted as .d.ts. Splitting types to .d.mts is tracked as a separate follow-up.

Closes #8396

Test plan

  • pnpm --filter @clerk/nextjs build succeeds — dist/esm/package.json contains "type": "module", dist/esm/server/index.rsc.js is emitted.
  • pnpm --filter @clerk/nextjs lint:attw passes with the swapped ignore rule.
  • pnpm --filter @clerk/nextjs lint:publint passes (pre-existing warnings unchanged).
  • pnpm --filter @clerk/nextjs test — no new regressions. Snapshot test extended to cover both default and react-server surfaces of ./server.
  • Reporter's exact dist/esm lacks 'type: module' metadata — breaks @clerk/nextjs on Node 22+ under type:module consumers #8396 repro (tsx + import { clerkClient } from '@clerk/nextjs/server' under "type": "module") now prints clerkClient typeof: function.
  • Minimal Next 15.5.15 app with a server component that calls await auth() builds cleanly — previously failed with Module "server-only" cannot be imported from a Client Component module.

Add `"type": "module"` to `package.esm.json` so `dist/esm/package.json`
declares the bundle as ESM. Node 22+ and tsx consumers under
`"type": "module"` can now import named exports from
`@clerk/nextjs/server` without hitting SyntaxError at instantiate time.

Swap `lint:attw --ignore-rules unexpected-module-syntax` (no longer
needed) for `--ignore-rules false-cjs`, which fires because types are
still emitted as `.d.ts`. The `.d.mts` split is tracked separately.

Closes #8396
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
clerk-js-sandbox Skipped Skipped Apr 24, 2026 4:52pm

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 24, 2026

🦋 Changeset detected

Latest commit: e9c5776

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/nextjs Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 699fd6ca-cd1d-48ad-b63c-5b3e27b9555c

📥 Commits

Reviewing files that changed from the base of the PR and between ca51781 and 84fedae.

📒 Files selected for processing (1)
  • packages/nextjs/src/server/__tests__/exports.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/nextjs/src/server/tests/exports.test.ts

📝 Walkthrough

Walkthrough

Adds Changesets release notes and marks the ESM build as an ES module by adding "type": "module" to packages/nextjs/package.esm.json. Updates package.json exports for "./server" to add a react-server conditional with separate ESM/CJS entrypoints and adjusts the types path. Replaces a wildcard re-export in packages/nextjs/src/experimental.ts with explicit named value and type-only exports. Introduces packages/nextjs/src/server/index.rsc.ts and removes direct re-exports of auth/currentUser from the pages-safe ./index so those APIs are provided via the react-server condition. auth and currentUser (app-router) now import 'server-only' at module top level. Tests updated to snapshot both export conditions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(nextjs): declare dist/esm as ESM and split ./server by react-server condition' clearly summarizes the two main changes: adding ESM metadata and restructuring the server exports.
Description check ✅ Passed The description comprehensively explains both changes and their rationale, directly relating to the changeset with clear context about Next 15 compatibility and ESM resolution fixes.
Linked Issues check ✅ Passed The PR fully addresses #8396 requirements: adds 'type:module' to dist/esm/package.json for Node 22+ ESM resolution, splits ./server by react-server condition to prevent Next 15 prerender regression, and imports server-only statically for webpack analyzability.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objectives: ESM metadata addition, react-server export condition split, static server-only imports, test updates, and lint rule adjustment. No unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 24, 2026

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8397

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8397

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8397

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8397

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8397

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8397

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8397

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8397

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8397

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8397

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8397

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8397

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8397

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8397

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8397

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8397

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8397

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8397

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8397

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8397

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8397

commit: 84fedae

Webpack's next-flight-loader rejects 'export *' in client boundaries
once a package declares itself as ESM. With the type:module sidecar now
in place, the existing re-export chain through
'@clerk/nextjs/experimental' broke Next.js 15 app builds that imported
from it in client components.

Spell out the named exports to satisfy the client-boundary rule while
keeping the public API intact.
…statically

Fixes the Next 15 prerender regression that the dist/esm sidecar introduced.

- Split `./server` into a pages-safe `index.ts` and an RSC-layer `index.rsc.ts`.
  The RSC variant re-exports the pages-safe surface plus `auth` and `currentUser`,
  which are now only resolvable under the `react-server` export condition.
- Replace the lazy `require('server-only')` calls in `auth.ts` and `currentUser.ts`
  with a top-of-file `import 'server-only'`. Safe because those modules only load
  under the `react-server` condition; webpack can analyze the static import in
  both CJS and ESM module classifications.
- Update `./server` exports condition map and extend the exports snapshot test to
  cover both the default and react-server surfaces.
@jacekradko jacekradko changed the title fix(nextjs): declare dist/esm as ESM via type:module sidecar fix(nextjs): declare dist/esm as ESM and split ./server by react-server condition Apr 24, 2026
vitest has no react-server resolve condition, so importing the RSC
surface transitively loads auth.ts and crashes on the client-facing
server-only export. Stubbing the module keeps the test focused on
verifying the re-export surface.
Vitest keys nested describes with ' > ' between every level including
the final `it`. The initial snapshot file was missing the separator
between the inner describe and the test name, so CI (which refuses to
auto-create snapshots) reported the tests as mismatched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant