Skip to content

@sentry/react-router: node entry statically re-exports the Vite plugin, pulling @sentry/vite-plugin and @sentry/cli into the server runtime graph #23495

Description

@syarig

Which SDK are you using?

@sentry/react-router

SDK Version

10.70.0

Problem

The Node entry of @sentry/react-router statically re-exports the Vite plugin and its build-time helpers:

// build/esm/index.server.js:10-12
export { sentryReactRouter } from './vite/plugin.js';
export { sentryOnBuildEnd } from './vite/buildEnd/handleOnBuildEnd.js';
export { makeConfigInjectorPlugin } from './vite/makeConfigInjectorPlugin.js';

Because these are static ESM re-exports, a plain server-side import * as Sentry from "@sentry/react-router" — the exact thing the docs tell you to put in instrument.server.mjs and load with --import — eagerly pulls @sentry/vite-plugin, @sentry/cli and glob into the runtime import graph of the production server.

These packages are only meaningful at build time (source map upload). For deployments that don't upload source maps they are pure dead weight, and even for those that do, they have no business being resolved inside the running server process.

Measurements

Node v22.22.3, same machine, warm FS cache, n=3 each:

import time
@sentry/react-router (node entry) 251 / 263 / 272 ms
@sentry/node 230 / 237 / 253 ms

So the marginal cost is roughly 20 ms of cold start. Small, but it is paid on every container start, and it is entirely avoidable.

Inspecting module._cache after await import('@sentry/react-router') confirms the build-time graph is actually loaded — 5 files / ~32 KB from @sentry/cli alone, e.g.:

node_modules/@sentry/cli/js/index.js
node_modules/@sentry/cli/js/helper.js
node_modules/@sentry/cli/js/releases/index.js

Why it can't be worked around

The package exports map only has two entries:

{
  "./package.json": "./package.json",
  ".":            { "node": { "import": "./build/esm/index.server.js", ... } },
  "./cloudflare": { ... }
}

There is no ./vite-style subpath, so a consumer cannot opt out by importing a narrower entry point. Bundling isn't an option either: bundling the SDK breaks OpenTelemetry's auto-instrumentation, since the patch targets become copies inside the bundle. Reaching for @sentry/node directly loses the React Router flavored init from ./server/sdk.js.

The same graph is also pulled in through wrapSentryHandleRequest, which is imported from application code (entry.server.tsx), so it is not limited to the instrumentation file.

Suggested fix

Move the Vite/build-time exports behind a dedicated subpath, e.g. @sentry/react-router/vite, so vite.config.ts imports sentryReactRouter / sentryOnBuildEnd from there and the server entry stops re-exporting them. This mirrors what several other Sentry meta-packages already do and would be a small, mechanical change.

If keeping them on the root entry is required for backwards compatibility, making the re-exports lazy (or marking the subgraph sideEffects: false so bundlers can drop it) would at least help the bundled case — though it would not help the --import case, which is the documented setup.

Expected Result

A server-side import * as Sentry from "@sentry/react-router" should not resolve @sentry/vite-plugin, @sentry/cli or glob.

Actual Result

They are eagerly loaded into the running server's module graph.

Metadata

Metadata

Assignees

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions