Repository navigation
@sentry/nuxt 11: CommonJS packages inlined for build-time instrumentation break in the Nitro bundle #24775
Description
This issue was written with Claude Opus 5.5 (Anthropic) and reviewed by a human before it was filed.
Is there an existing issue for this?
- I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
- I have reviewed the documentation https://docs.sentry.io/
- I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/nuxt
SDK Version
11.0.0
Framework Version
Nuxt 4.5.2 (nitropack 2.13.4, node-server preset)
Link to Sentry event
No response
Reproduction Example/SDK Setup
nuxt.config.ts registers @sentry/nuxt/module and does not set buildTimeInstrumentation, so build-time instrumentation is on. A sentry.server.config.ts exists:
import { init } from "@sentry/nuxt";
init({
dsn: process.env.NUXT_PUBLIC_SENTRY_DSN,
// further runtime options (integrations, beforeSend, ...) omitted; they don't affect bundling
});Steps to Reproduce
Observed in our project build. The minimal reproduction below has not been run on its own.
- Create a Nuxt 4 app and install
@sentry/nuxt@11.0.0andmongoose@9.10.2. - Add
modules: ["@sentry/nuxt/module"]tonuxt.config.tsand create asentry.server.config.tsthat callsSentry.init({ dsn: "..." }). - Use mongoose in server code that gets bundled, e.g.
server/plugins/db.ts:import mongoose from "mongoose"; export default defineNitroPlugin(() => { console.log(mongoose.version); });
nuxt buildnode .output/server/index.mjs(we used Node.js 26)- For the second failure (see Actual Result), apply the
requireReturnsDefaultworkaround below, rebuild, and have the pluginawait mongoose.connect(uri)to a MongoDB user with?authSource=admin&authMechanism=SCRAM-SHA-1. We reproduced this with our project build against a localmongo:8.
Expected Result
The server starts and connects to MongoDB.
Actual Result
The server throws while chunks/nitro/nitro.mjs is being evaluated, so it never starts listening:
TypeError: mquery is not a constructor
at file:///app/.output/server/chunks/nitro/nitro.mjs:97168:21
The failing statement is mongoose's top-level Query.prototype = new mquery();.
With that one worked around (requireReturnsDefault, see below), the server logs Listening, but the app's startup plugin can't connect to MongoDB. The error is raised in the SCRAM-SHA-1 path of the handshake. Staging log, abridged; a local build of the same commit against a mongo:8 with authMechanism=SCRAM-SHA-1 gives the same trace:
Application startup error MongoRuntimeError: Node.js crypto module is required for SCRAM-SHA-1 authentication
at passwordDigest (file:///app/.output/server/chunks/nitro/nitro.mjs:55292:16)
...
[cause]: ReferenceError: require is not defined
at passwordDigest (file:///app/.output/server/chunks/nitro/nitro.mjs:55289:10)
Additional Context
Cause
In 11.0.0, setupOrchestrion() adds every name in INSTRUMENTED_MODULE_NAMES (among them mongoose, mongodb and ioredis) plus standard-as-callback to nitro.externals.inline. These packages are CommonJS, and most of their CommonJS dependencies stay external (mquery, kareem, mpath, ms, sift, denque, cluster-key-slot and @ioredis/commands are all in .output/server/node_modules).
Nitro configures @rollup/plugin-commonjs with esmExternals: (id) => !id.startsWith("unenv/") and requireReturnsDefault: "auto". So each require() of an external from bundled CommonJS code becomes:
import * as mquery$1 from 'mquery';
const require$$N = /*@__PURE__*/getDefaultExportFromNamespaceIfNotNamed(mquery$1);getDefaultExportFromNamespaceIfNotNamed only unwraps default if it is the only key:
function getDefaultExportFromNamespaceIfNotNamed (n) {
return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1 ? n['default'] : n;
}Since Node.js v23.0.0 (nodejs/node#53848), CommonJS namespaces also carry a 'module.exports' export next to default, so the check never passes for a CommonJS package:
Object.keys(await import('mquery')); // [ 'default', 'module.exports' ] (Node.js 26.2.0)Node builtins fail the check too, because their namespaces have named exports. The bundled code therefore gets the namespace object instead of module.exports. The comment on IORedisDependencies in packages/nuxt/src/vite/orchestrion.ts describes this exact problem ("Leaving it external makes Nitro resolve the default export as a namespace object"), but the fix there only covers standard-as-callback.
What breaks in our bundle
Every package below receives the namespace. Whether that breaks depends on how the bundled code uses it.
| Package | Required by | Use in bundled code | Effect |
|---|---|---|---|
mquery 6.0.0 |
mongoose 9.10.2 | new mquery() at module top level |
crash at startup (observed) |
kareem 3.4.0 |
mongoose | new Kareem() |
would throw |
mpath 0.9.0 |
mongoose | mpath.get(...), mpath.set(...) (namespace has only default and module.exports) |
would throw |
ms 2.1.3 |
mongoose | ms(val) |
would throw |
sift 17.1.3 |
mongoose | require('sift').default |
works (.default on the namespace is module.exports) |
denque 2.1.0 |
ioredis 6.0.0 | new Deque() |
would throw |
cluster-key-slot 1.1.1 |
ioredis | calculateSlot(key) |
would throw |
@ioredis/commands 2.0.0 |
ioredis | .exists, .getKeyIndexes, .hasFlag, .list (all detected as named exports) |
works |
assert (builtin) |
redis-errors 1.2.0 (bundled) | assert(buffer) in the ParserError constructor |
would throw assert is not a function; ioredis 6.0.0 never constructs ParserError |
Only the mquery crash was observed, because it happens first. The "would throw" rows follow from calling or constructing a namespace object. redis-errors is bundled because Nitro's string matchers are prefix matches, so the inlined name redis also matches redis-errors. events and stream also resolve to namespaces, but our bundle only reads properties from them (.EventEmitter, once, .Readable, .Writable).
Second effect: require() inside try stays a bare require
By default (ignoreTryCatch: true, not overridden by Nitro), @rollup/plugin-commonjs leaves a require() of an external module inside a try block unconverted, because such calls usually load optional dependencies. Builtins count as external. Nitro emits ESM, where require is not defined, so each such call throws a ReferenceError and ends up in the surrounding catch. mongodb 7.6.0 loads the crypto builtin this way in passwordDigest(), which it only calls for SCRAM-SHA-1; SCRAM-SHA-256 doesn't reach it. Abridged from lib/cmap/auth/scram.js:
let nodeCrypto;
try {
nodeCrypto = require('crypto');
}
catch (e) {
throw new error_1.MongoRuntimeError('Node.js crypto module is required for SCRAM-SHA-1 authentication', { cause: e });
}With 10.75.1, nothing in our config inlined mongodb (orchestrion was opt-in, and we set no externals.inline), so mongodb was loaded from node_modules as CommonJS, where require exists.
Without the ignoreTryCatch setting, our bundle has ten bare require() calls inside try:
- mongodb:
crypto(above), plus the optional peer dependencies inlib/deps.js, which arekerberos,@mongodb-js/zstd,@aws-sdk/credential-providers,gcp-metadata,snappy,socksandmongodb-client-encryption.socks2.8.10 andgcp-metadata9.0.4 are installed in our.output/server/node_modules, but the bundled mongodb can't load them. - mongoose (
lib/tracing.js) and ioredis (built/tracing.js): a fallbackrequire('node:diagnostics_channel'). It is only reached whenprocess.getBuiltinModuleis missing, so it doesn't run on Node.js 26.
Regression
Our previous release used @sentry/nuxt 10.75.1 with the same Nuxt 4.5.2, nitropack 2.13.4, @rollup/plugin-commonjs 29.0.3, ioredis 6.0.0 and mongoose 9.10.1 (also with mquery 6.0.0), and it deployed and started. In 10.75.1, setupOrchestrion only ran with _experimental.useDiagnosticsChannelInjection, which we never set. The 11.0.0 release also updated other dependencies; we did not bisect.
Workaround
In nuxt.config.ts, use the default export for builtins and for the external CommonJS dependencies:
import { isBuiltin } from "node:module";
const EXTERNAL_COMMONJS_REQUIRES = new Set([
"@ioredis/commands", "cluster-key-slot", "denque", "kareem", "mpath", "mquery", "ms", "sift",
]);
export default defineNuxtConfig({
nitro: {
commonJS: {
requireReturnsDefault: (id) =>
isBuiltin(id) || EXTERNAL_COMMONJS_REQUIRES.has(id) ? true : "auto",
// Convert builtin require()s inside try blocks to imports; optional packages stay unresolved.
ignoreTryCatch: (id) => !isBuiltin(id),
},
},
});sift and @ioredis/commands work either way and are listed only to keep the list complete. With both settings, the built chunks/nitro/nitro.mjs imports all eight packages as default imports. None of its remaining getDefaultExportFromNamespaceIfNotNamed calls wraps a CommonJS external. mongodb's require('crypto') becomes import … from 'crypto'. The seven optional require()s in mongodb's lib/deps.js stay bare, so those packages still behave as not installed. Against the same local mongo:8 (authMechanism=SCRAM-SHA-1), this build starts, authenticates (mongod logs Successfully authenticated, mechanism SCRAM-SHA-1) and serves requests.
The workaround is validated in our staging deployment (Node.js 26, node:26-slim image, a real MongoDB deployment, Redis). The release with both settings starts, passes the deployment's health checks and serves traffic. Two earlier releases failed there: the one with neither setting failed with the mquery error, and the one with only requireReturnsDefault failed with the SCRAM error.
Possible fixes (suggestions, not tested)
- Extend
IORedisDependenciesto cover the CommonJS dependencies of every force-inlined package. Builtins can't be inlined, so they would stay affected. - Have
setupOrchestrion()setnitro.commonJS.requireReturnsDefault(as a function) to returntruefor builtins and for the CommonJS externals that force-inlined packagesrequire(). This also covers builtins. - For
require()insidetryin inlined packages: convert builtins viaignoreTryCatch, or give the inlined code a realrequire(e.g.createRequire(import.meta.url)) so that optional dependencies that are installed can still load.
Either way, a hand-maintained list goes stale when an instrumented package gains a new CommonJS dependency.
Priority
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.
Metadata
Metadata
Assignees
Projects
- StatusShow more project fieldsNo status
Activity
linear-code commentedon Sep 27, 2026
JS-3826
s1gr1d commentedon Sep 28, 2026
Hello, thanks for writing in!
I created a PR to fix it: #24799
But this might also need upstream fixes: rollup/plugins#2027
fix(nuxt): Fix CommonJS interop for force-inlined instrumented packag…
github-actions commentedon Oct 1, 2026
A PR closing this issue has just been released 🚀
This issue was referenced by PR #24799, which was included in the 11.2.0 release.