Skip to content

@sentry/nuxt 11: CommonJS packages inlined for build-time instrumentation break in the Nitro bundle #24775

Description

@Anton-Honda

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?

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.

  1. Create a Nuxt 4 app and install @sentry/nuxt@11.0.0 and mongoose@9.10.2.
  2. Add modules: ["@sentry/nuxt/module"] to nuxt.config.ts and create a sentry.server.config.ts that calls Sentry.init({ dsn: "..." }).
  3. 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);
    });
  4. nuxt build
  5. node .output/server/index.mjs (we used Node.js 26)
  6. For the second failure (see Actual Result), apply the requireReturnsDefault workaround below, rebuild, and have the plugin await mongoose.connect(uri) to a MongoDB user with ?authSource=admin&authMechanism=SCRAM-SHA-1. We reproduced this with our project build against a local mongo: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 in lib/deps.js, which are kerberos, @mongodb-js/zstd, @aws-sdk/credential-providers, gcp-metadata, snappy, socks and mongodb-client-encryption. socks 2.8.10 and gcp-metadata 9.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 fallback require('node:diagnostics_channel'). It is only reached when process.getBuiltinModule is 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 IORedisDependencies to cover the CommonJS dependencies of every force-inlined package. Builtins can't be inlined, so they would stay affected.
  • Have setupOrchestrion() set nitro.commonJS.requireReturnsDefault (as a function) to return true for builtins and for the CommonJS externals that force-inlined packages require(). This also covers builtins.
  • For require() inside try in inlined packages: convert builtins via ignoreTryCatch, or give the inlined code a real require (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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions