Skip to content

@sentry/nextjs 10.72.0+ throws "The URL must be of scheme file" at module scope under jsdom/happy-dom #23789

Description

@Srbino

Is there an existing issue for this?

  • I have checked for existing issues
  • I have reviewed the documentation
  • I am using the latest SDK release

Searched for fileURLToPath, must be of scheme file, code-transformer-bundler-plugins, and jsdom/happy-dom combinations before filing; nothing matched. #22794 and #23611 look like sibling symptoms of the same structural cause — the server entry eagerly pulling in the orchestrion bundler plugin — but neither covers this crash.

How do you use Sentry?

Sentry SaaS (sentry.io)

Which SDK are you using?

@sentry/nextjs

SDK Version

10.72.0 and 10.73.0 (10.71.0 is fine)

Framework Version

Next.js 16.3.3, Node 24.14.0. Reproduces with no framework at all — see below.

Link to Sentry event

N/A — this is a module-load crash, nothing reaches Sentry.

Reproduction Example/SDK Setup

Importing @sentry/nextjs throws at module scope whenever a document global exists — which is the normal state of affairs under jsdom or happy-dom, i.e. in any Vitest or Jest test using a DOM environment.

No test runner or bundler is needed to see it:

mkdir sentry-repro && cd sentry-repro && npm init -y
npm i @sentry/nextjs@10.73.0

cat > repro.cjs <<'JS'
globalThis.document = { baseURI: 'http://localhost:3000/' }
require('@sentry/nextjs')
console.log('loaded OK')
JS

node repro.cjs                              # throws
node -e "require('@sentry/nextjs')"         # control: prints loaded OK

Steps to Reproduce

  1. Install @sentry/nextjs@10.72.0 or @10.73.0.
  2. Define any document global with a baseURI — or just run a Vitest suite with environment: 'happy-dom' (or Jest with testEnvironment: 'jsdom') that imports the SDK, directly or transitively.
  3. Load the module.

Expected Result

The module loads. 10.71.0 does, under an identical setup.

Actual Result

TypeError [ERR_INVALID_URL_SCHEME]: The URL must be of scheme file
    at Object.fileURLToPath (node:internal/url:1608:11)
    at Module.<anonymous> (node_modules/@sentry/server-utils/build/cjs/vendored/
        @apm-js-collab/code-transformer-bundler-plugins/dist/esm/webpack.js:9:61)
    at Module._compile (node:internal/modules/cjs/loader:1812:14)

The node export condition of @sentry/nextjs reaches @sentry/server-utils/build/cjs/orchestrion/bundler/webpack.js, which pulls in the vendored @apm-js-collab/code-transformer-bundler-plugins. That module computes LOADER_PATH at module scope from a dual browser/Node guard:

var LOADER_PATH = path.resolve(path.dirname(url.fileURLToPath(
  typeof document === 'undefined'
    ? pathToFileURL(__filename).href
    : (_documentCurrentScript && ... || new URL('vendored/.../webpack.js', document.baseURI).href)
)), "..", "cjs", "webpack-loader.cjs");

The guard is typeof document === 'undefined', so it treats "a document exists" as "this is a browser". Under jsdom/happy-dom that is false — the code is running in Node, and __filename is right there and valid. It takes the browser branch, builds http://localhost:3000/vendored/... from document.baseURI, and hands that to fileURLToPath, which only accepts file:.

Two things make this worse than it might look:

  • It throws at module scope, so it is not catchable by the importer and takes down the whole module graph. In our suite, 11 test files stopped loading; every assertion inside them still passed, so the failure did not look like a dependency problem at all.
  • It arrives through lockfile regeneration, not a reviewed bump. Our package.json says ^10.70.0 and never changed; a routine Dependabot PR for unrelated packages re-resolved the tree and moved the SDK from 10.71.0 to 10.72.0 as a side effect.

Suggested fix

Prefer a positive Node check over an absence-of-document check, e.g. gate on typeof __filename !== 'undefined' (or process.versions?.node) first and fall back to the document-based path only when there is genuinely no module path available. The bundler emitting the dual guard is @apm-js-collab/code-transformer-bundler-plugins, so the fix may belong there rather than in this repo — but it ships vendored inside @sentry/server-utils, which is where it reaches users.

Deferring the LOADER_PATH computation until the plugin is actually used would also fix it, and would additionally address the class of problem in #22794 and #23611: a bundler-time plugin should not be doing filesystem path resolution just because someone imported the SDK.

Workaround

Pinning below 10.72.0 ("@sentry/nextjs": "<10.72.0" in pnpm.overrides). Setting resolve.conditions: ['browser'] in Vitest also gets past it, but only by resolving the client build, which then wants next/router — absent in an App Router project — and it changes resolution for every other package too.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions