Skip to content

@sentry/nextjs server entry re-exports withSentryConfig, pulling the orchestrion webpack plugin's WASM into the runtime bundle (CompileError on Cloudflare Workers) #22794

Description

@isaacrowntree

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/nextjs

SDK Version

10.68.0

Framework Version

Next.js 16.2.11, @opennextjs/cloudflare 1.20.2, node 26.2.0, workerd compatibility_date 2026-04-14

Link to Sentry event

Private project — full stack and tags reproduced below.

Reproduction Example/SDK Setup

A Next.js app deployed to Cloudflare Workers via OpenNext, with the standard
instrumentation.ts setup:

// src/instrumentation.ts
import * as Sentry from "@sentry/nextjs";

export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("../sentry.server.config");
  }
}

Steps to Reproduce

  1. import * as Sentry from "@sentry/nextjs" from any server module (instrumentation.ts, sentry.server.config.ts, a route handler).
  2. Build for Cloudflare Workers (opennextjs-cloudflare build) and deploy.
  3. Trigger a cold start — any request will do.

Expected Result

The runtime bundle contains only runtime code. Nothing attempts
WebAssembly.compile() at module-evaluation time, since Cloudflare Workers
forbids runtime WASM compilation.

Actual Result

Every cold start raises an unhandled promise rejection:

CompileError: WebAssembly.compile(): Wasm code generation disallowed by embedder
  at Context.commonJsRequire
  at getOrInstantiateModuleFromParent
  at instantiateModule
  at module evaluation

Tagged unhandledPromiseRejection: true, with no route frames — the stack is
module evaluation only. In our project this produced 50 events over four days,
one per cold start, attributed to whichever request happened to warm the
isolate (44 of 50 have an empty transaction tag).

Root cause

@sentry/nextjs's server runtime entry re-exports the build-time config
helper, and that export's module graph reaches a bundler plugin containing an
inlined WASM module:

node_modules/@sentry/nextjs/build/esm/index.server.js:1
  export { withSentryConfig } from './config/withSentryConfig/index.js';
    -> config/withSentryConfig/buildTime.js:4,6
         import { getTracingHooksDirectory }    from '@sentry/server-utils/orchestrion/webpack';
         import { sentryOrchestrionWebpackPlugin } from '@sentry/server-utils/orchestrion/webpack';
      -> @sentry/server-utils/build/esm/orchestrion/bundler/webpack.js
        -> @apm-js-collab/code-transformer-bundler-plugins@0.7.1

The plugin compiles an inlined base64 WASM module (a CJS/ESM lexer) at module**
**scope, unawaited:

g = WebAssembly.compile(d()).then(WebAssembly.instantiate).then(({exports: e}) => { u = e })

On Node that promise resolves and nobody notices. On Workers WebAssembly.compile
throws, and because the promise is never awaited it surfaces as an unhandled
rejection during module evaluation — with a stack that names no application code,
which makes it quite hard to attribute.

@sentry/cloudflare is not the culprit: build/esm/sdk.js:33-39 explicitly reads
channel integrations off a global marker rather than importing them, with a comment
saying this is "so bundles built without the plugin don't ship the code." That part
works as designed. The leak is the Next.js server entry.

What I verified

  • The only dependency path to the plugin is @sentry/cloudflare -> @sentry/server-utils -> @apm-js-collab/code-transformer-bundler-plugins@0.7.1, but the inclusion comes from the import chain above.
  • .next/server contains the plugin in 12 chunks, including the base64 WASM blob.
  • Control build: removing withSentryConfig from next.config.ts entirely and rebuilding leaves all 12 chunks unchanged. The build-time config file is not what pulls it in — importing @sentry/nextjs from a server module is.
  • @sentry/server-utils' main entry does not reach the plugin (it is confined to the ./orchestrion/* subpath exports), and the ./orchestrion barrel re-exports only channel integrations. So the subpath import in buildTime.js is the single crossing point.

Why this may matter beyond Workers

#22632 states the design assumption directly: "orchestrion only ships via
@sentry/node/{vite,webpack,rollup,esbuild} and framework server builds." That
assumption doesn't hold here — withSentryConfig sitting on index.server.js
puts the webpack plugin one import * as Sentry from "@sentry/nextjs" away from
any server bundle. Cloudflare Workers just makes it loud, because runtime WASM
compilation is a hard error there. Elsewhere it is silent bundle weight.

Suggested fix

Move withSentryConfig off the runtime server entry, or make
config/withSentryConfig/buildTime.js import the bundler plugin lazily
(await import(...) inside the function that uses it) so the WASM module isn't
reachable from module-evaluation of the runtime entry.

Workaround for others hitting this

Alias @sentry/server-utils/orchestrion/* to an empty module in the Worker build,
or import the runtime pieces from @sentry/cloudflare instead of @sentry/nextjs
where possible.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status
    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions