Skip to content

fix(tanstackstart-react): workerd/worker export conditions resolve to @sentry/node, breaking Cloudflare Workers #20038

@jmalmo

Description

@jmalmo

Is there an existing issue for this?

How do you use Sentry?

Sentry SaaS (sentry.io)

Which SDK are you using?

@sentry/tanstackstart-react

SDK Version

10.46.0

Framework Version

TanStack Start ~1.166 (React 19, SSR via Nitro ~3.0)

Link to Sentry event

No response

Reproduction Example/SDK Setup

The core claim — that the workerd/worker conditions resolve to a file that re-exports @sentry/node — is verifiable directly from the package metadata and source without any app:

  1. Inspect packages/tanstackstart-react/package.jsonworkerd, worker, and node all resolve to index.server.js
  2. Inspect build/esm/index.server.js — does export * from '@sentry/node'

For the runtime failure:

  1. Create a TanStack Start app deployed to Cloudflare Workers (Nitro cloudflare-module preset, noExternals: true)
  2. Install @sentry/tanstackstart-react@10.46.0
  3. Import in router.tsx (SSR entry): import * as Sentry from '@sentry/tanstackstart-react'
  4. Set ssr.resolve.conditions: ['workerd', 'worker', 'browser', 'import', 'module', 'default'] in Vite config
  5. Build and deploy — the Worker returns HTTP 500 with "Cannot initialize ExportedHandler"

Because Nitro uses noExternals: true for Workers (no node_modules at runtime), the package is not externalized. Vite resolves it using ssr.resolve.conditions (not ssr.resolve.externalConditions), so the workerd condition takes effect and routes to index.server.js.

Steps to Reproduce

@sentry/tanstackstart-react advertises workerd and worker export conditions in its package.json, suggesting compatibility with Cloudflare Workers and similar edge runtimes. However, all three server-side conditions (workerd, worker, node) resolve to the exact same file — index.server.js — which re-exports @sentry/node.

The Node-orientation is not limited to the barrel re-export. The server source modules themselves import directly from @sentry/node:

When bundled for Workers (where @sentry/tanstackstart-react is non-externalized and resolved under the workerd condition), this pulls the @sentry/node dependency tree — including @opentelemetry/instrumentation-undici and transitive node:* built-in imports — into the Worker bundle. While Cloudflare Workers with nodejs_compat do support many Node APIs to varying degrees, in our deployment this caused module evaluation failure: the Worker's default export became null, producing "Cannot initialize ExportedHandler" at runtime with no stack trace. The exact failure mechanism likely depends on which node:* shims are partial or missing for the given compatibility date, but the root issue is that Workers resolves to a Node SDK entry that was never designed for this runtime.

package.json exports (v10.46.0):

{
  "exports": {
    ".": {
      "workerd": {
        "import": "./build/esm/index.server.js",
        "require": "./build/cjs/index.server.js"
      },
      "worker": {
        "import": "./build/esm/index.server.js",
        "require": "./build/cjs/index.server.js"
      },
      "browser": {
        "import": "./build/esm/index.client.js",
        "require": "./build/cjs/index.client.js"
      },
      "node": {
        "import": "./build/esm/index.server.js",
        "require": "./build/cjs/index.server.js"
      }
    }
  }
}

workerd, worker, and node all resolve to index.server.js. Only browser resolves to index.client.js. The same export map is present on the develop branch, so this is a current packaging issue.

Expected Result

The workerd and worker export conditions should not resolve to code that re-exports @sentry/node.

Minimal fix — remove misleading conditions:

Remove the workerd and worker export conditions from package.json, since the package does not currently provide Workers-compatible server code. This is a packaging stopgap, not a fully non-breaking resolution:

  • Fallback behavior is toolchain-dependent. Bundlers whose active condition set includes browser (e.g., Vite with ['workerd', 'worker', 'browser', ...]) will fall through to index.client.js, which is safe. But this is not guaranteed across all resolvers — Node's package docs recommend a default branch for unknown runtimes. Whether to add default pointing to the client entry is a design call for the Sentry team.
  • Export surface change. The browser/client entry exports stubs for wrapMiddlewaresWithSentry, sentryGlobalRequestMiddleware, and sentryGlobalFunctionMiddleware, but it does not export wrapFetchWithSentry. Consumers who import that helper would get a build error. The shared index.types.ts still re-exports the full server API surface, so TypeScript types and runtime exports would diverge for Workers consumers.

Despite these caveats, removing the conditions is strictly better than the current state: it stops actively directing Workers bundlers to Node code, and the practical impact is limited since the server helpers don't work on Workers anyway.

Longer-term — proper Workers entry (follow-up):

A proper fix would create Worker-specific server modules that use @sentry/cloudflare instead of @sentry/node. This is non-trivial since @sentry/node is imported directly by multiple server source files, not just the barrel. This would likely be a separate design effort, potentially tracked alongside JS-1388.

Actual Result

Worker crashes with "Cannot initialize ExportedHandler" — silent module evaluation failure, no stack trace, no useful output from wrangler tail.

Condition resolved Server entry What gets bundled Result on Workers
workerd index.server.js @sentry/node + OpenTelemetry + node:* Silent crash
worker index.server.js @sentry/node + OpenTelemetry + node:* Silent crash
browser index.client.js @sentry/browser (no Node deps) Works
node index.server.js @sentry/node Works (Node.js)

Workaround: Replace @sentry/tanstackstart-react with @sentry/react for runtime imports. Both export init, ErrorBoundary, and tanstackRouterBrowserTracingIntegration. This preserves client-side Sentry but loses the TanStack Start server helpers. For server-side Workers tracking, use @sentry/cloudflare separately. The Vite plugin (@sentry/tanstackstart-react/vite) is build-time only and unaffected.

Related issues

  • JS-1388 — Investigate TanStack Start React Cloudflare support
  • #12620 — Sentry Cloudflare Workers SDK
  • JS-348 — Support Next.js on Cloudflare Workers (OpenNext) (same pattern with @sentry/nextjs)
  • JS-1481 — Cannot resolve @sentry/nextjs on Cloudflare Workers (same class of bug)
  • JS-377 — React Router v7 deployed to Cloudflare Workers

Additional context

Note: We are aware the SDK setup docs note this is still alpha and warn that the setup "does not currently work for Cloudflare deployments." This issue is specifically about the misleading workerd/worker export conditions — they signal bundler-level compatibility that doesn't exist, causing a silent failure mode that is very difficult to diagnose.

Environment details: Cloudflare Workers paid plan, compatibility date 2025-09-01, nodejs_compat enabled. Bundling via Vite 7 (Rolldown) with Nitro noExternals: true.

We observed platform-dependent behavior: macOS local builds sometimes tree-shook enough undici code for the Worker to boot, while Linux CI included the full transport (~900 KiB), causing module evaluation failure. This variance is secondary to the export-map mismatch but made diagnosis significantly harder.

Metadata

Metadata

Assignees

No one assigned
    No fields configured for issues without a type.

    Projects

    Status

    Waiting for: Community

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions