Skip to content

Reduce @sentry/node startup cost by flattening the published build #25680

Open

Description

@mydea

require('@sentry/node') loads 589 CommonJS modules and takes about 92–100 ms on Node 24.21, measured against the local CJS build. NODE_COMPILE_CACHE brought no measurable gain, so the time goes to resolving, reading and evaluating many small files rather than compiling them. The packages are built with preserved modules, so each source file becomes its own published file. By module count the largest contributors are @sentry/server-utils, @sentry/core, @sentry/server-runtime-injection (its bundled parser and code generator) and @opentelemetry/api.

This cost lands on every cold start (serverless functions, CLIs, autoscaling containers).

Proposal

Reduce the cost through the build output shape. AGENTS.md rules out lazy loading as a fix, so imports stay static:

  • Publish flattened entry files (instead of preserved modules) for @sentry/node, @sentry/server-utils and @sentry/server-runtime-injection, with the same exports maps and entry points.
  • Check that nothing which only matters for runtime injection is evaluated on the main entry when it doesn't need to be, using a static restructure rather than lazy require.
  • Measure before and after with the count-based gate proposed in the perf issue (modules loaded, import time).

Risks to check

  • Deep imports into build/ paths
  • @vercel/nft / Next.js file tracing
  • The chunk layout that server-runtime-injection's hook.js relies on
  • Dual-package hazards between the CJS and ESM builds

Related: #19885 takes a different approach to the same problem: a separate @sentry/core/light entry for lightweight consumers.

Activity

  1. linear-code commented on Oct 23, 2026

    @linear-code
  2. self-assigned this
    on Oct 29, 2026
  3. isaacs commented on Oct 29, 2026

    @isaacs
    Member

    A few thoughts on this:

    1. If we include @sentry/core in the set, then we drop the resulting loaded module count down from 269 to 61. (It's 606 now, so it's a big win either way.) A lot of Node consumers resolve core through the require condition, so we could flatten only core's CJS build. The ESM build that browser bundlers and React Native use would stay preserved, which keeps the size-limit and tree-shaking risk away.

    2. we should clarify that each package keeps its @sentry/* deps external. I tried bundling core into @sentry/node, and core loaded twice: once inside the bundle, and once through @sentry/opentelemetry. So there's a dual-instance hazard to watch out for.

    3. This doesn't account for shared chunks. Each package has several entries (core has index, server and browser; server-utils has 10). Without preserved modules, rollup emits shared chunks with hashed names such as trace-kYyA2XQ_.js. We need fixed chunkFileNames, so file names stay stable for nft tracing and for hook.mjs's relative imports.

    4. This should likely happen after the work for JS-3989, I'm assuming? Makes more sense to stack on the guard and other work to prevent code getting where it shouldn't.

  4. added 2 commits that reference this issue on Oct 31, 2026
    b61e94d
    29a8cd2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions