Package: @sentry/nextjs@10.29.0
Node: v22.18.0
Summary
exports["."]["node"] is a bare string pointing at the CJS build. Every other platform condition in the same map splits import/require; node does not. Because node is active for any Node process and is declared before import, any ESM consumer under a plain-Node loader resolves build/cjs/index.server.js rather than build/esm/index.server.js — and cjs-module-lexer cannot see the bindings that arrive via export * from @sentry/core / @sentry/node, so the resulting namespace is partial.
The manifest
Read directly from the installed package:
browser and all four edge/worker conditions split import/require. node is the only platform condition that doesn't — which makes the "import" entry below it effectively dead code for Node, since node always matches first.
Reproduction
From any project with the package installed — no other setup:
$ node --input-type=module -e "console.log(import.meta.resolve('@sentry/nextjs'))"
file:///.../@sentry/nextjs/build/cjs/index.server.js # CJS, not ESM
$ node --input-type=module -e "import { addBreadcrumb } from '@sentry/nextjs'"
SyntaxError: Named export 'addBreadcrumb' not found. The requested module
'@sentry/nextjs' is a CommonJS module, which may not support all
module.exports as named exports.
$ node --input-type=module -e "import * as S from '@sentry/nextjs'; console.log(typeof S.init, typeof S.captureException, typeof S.isEnabled)"
function undefined undefined
Impact
Named imports fail loudly at link time, which is the safe case.
Namespace imports are the dangerous one. They link cleanly and the missing bindings are undefined, so the failure surfaces as TypeError: X is not a function at call time, arbitrarily far from the import. The detected/undetected split follows nothing a consumer can predict:
| present |
undefined |
init, startSpan, captureRequestError, withSentryConfig |
captureException, captureMessage, setTag, setContext, addBreadcrumb, isEnabled, isInitialized, consoleLoggingIntegration |
captureException is a particularly unhappy landing spot, since it is usually called from inside a catch block — so the resulting TypeError is frequently swallowed by the very error handler that invoked it, and the error is silently never reported.
This does not affect apps built by Next itself (webpack/turbopack prefer the import condition), nor Vite-based loaders. It affects plain-Node ESM tooling: scripts, CLIs, health-check probes and test harnesses that import the SDK outside the bundler.
A default import (import S from '@sentry/nextjs') recovers the full server-build surface, because it reads the runtime module.exports object rather than the lexer's static guess. That is a viable workaround, but consumers have to know to apply it, and it is easily "tidied" back into a namespace import by a later reader.
Suggested fix
Give node the same import/require split its sibling conditions already have:
Backwards-compatible for CJS consumers (require still resolves the CJS build), and it makes the existing "import" entry reachable for ESM ones.
Possibly related
#20038 — workerd/worker conditions resolving to @sentry/node, breaking Cloudflare Workers. Same class (an export condition resolving to the wrong build), different condition.
If this is intentional
If node deliberately pins CJS — for instance because OpenTelemetry instrumentation patching relies on require semantics — that would be very useful to have stated, since it makes the default-import workaround permanent rather than transitional. Given browser does split, the asymmetry currently reads as an oversight rather than a decision.
Package:
@sentry/nextjs@10.29.0Node: v22.18.0
Summary
exports["."]["node"]is a bare string pointing at the CJS build. Every other platform condition in the same map splitsimport/require;nodedoes not. Becausenodeis active for any Node process and is declared beforeimport, any ESM consumer under a plain-Node loader resolvesbuild/cjs/index.server.jsrather thanbuild/esm/index.server.js— andcjs-module-lexercannot see the bindings that arrive viaexport *from@sentry/core/@sentry/node, so the resulting namespace is partial.The manifest
Read directly from the installed package:
browserand all four edge/worker conditions splitimport/require.nodeis the only platform condition that doesn't — which makes the"import"entry below it effectively dead code for Node, sincenodealways matches first.Reproduction
From any project with the package installed — no other setup:
Impact
Named imports fail loudly at link time, which is the safe case.
Namespace imports are the dangerous one. They link cleanly and the missing bindings are
undefined, so the failure surfaces asTypeError: X is not a functionat call time, arbitrarily far from the import. The detected/undetected split follows nothing a consumer can predict:undefinedinit,startSpan,captureRequestError,withSentryConfigcaptureException,captureMessage,setTag,setContext,addBreadcrumb,isEnabled,isInitialized,consoleLoggingIntegrationcaptureExceptionis a particularly unhappy landing spot, since it is usually called from inside acatchblock — so the resultingTypeErroris frequently swallowed by the very error handler that invoked it, and the error is silently never reported.This does not affect apps built by Next itself (webpack/turbopack prefer the
importcondition), nor Vite-based loaders. It affects plain-Node ESM tooling: scripts, CLIs, health-check probes and test harnesses that import the SDK outside the bundler.A default import (
import S from '@sentry/nextjs') recovers the full server-build surface, because it reads the runtimemodule.exportsobject rather than the lexer's static guess. That is a viable workaround, but consumers have to know to apply it, and it is easily "tidied" back into a namespace import by a later reader.Suggested fix
Give
nodethe sameimport/requiresplit its sibling conditions already have:Backwards-compatible for CJS consumers (
requirestill resolves the CJS build), and it makes the existing"import"entry reachable for ESM ones.Possibly related
#20038 —
workerd/workerconditions resolving to@sentry/node, breaking Cloudflare Workers. Same class (an export condition resolving to the wrong build), different condition.If this is intentional
If
nodedeliberately pins CJS — for instance because OpenTelemetry instrumentation patching relies onrequiresemantics — that would be very useful to have stated, since it makes the default-import workaround permanent rather than transitional. Givenbrowserdoes split, the asymmetry currently reads as an oversight rather than a decision.