Summary
@sentry/sveltekit's server entry point statically re-exports the Vite build plugin, so importing the SDK in server code instantiates and evaluates the entire build-time module graph — including @babel/core and ~200 of its files.
This is deliberately not a request to move @sentry/vite-plugin out of dependencies — I've read #19360 and #18215 and understand the one-package decision. This is the narrower follow-up to what @Lms24 wrote in #19360:
Our Vite plugin should not be used at runtime and hence not be pulled into any JS bundles executed at runtime. If that's the case, it's likely a bug we need to fix.
It is pulled in, and the mechanism is a single static re-export.
The chain
build/esm/index.server.js:2 — the Node/default condition entry:
export { trackComponent } from './server/index.js';
export { sentrySvelteKit } from './vite/sentryVitePlugins.js'; // <-- build plugin, from the runtime entry
export * from '@sentry/node';
From there every link is a top-level static ESM import:
build/esm/index.server.js:2 export { sentrySvelteKit } from './vite/sentryVitePlugins.js'
build/esm/vite/sentryVitePlugins.js:7 import { makeCustomSentryVitePlugins } from './sourceMaps.js'
build/esm/vite/sourceMaps.js:3 import { sentryVitePlugin } from '@sentry/vite-plugin'
@sentry/vite-plugin index.mjs:3 import { sentryCliBinaryExists } from '@sentry/bundler-plugin-core'
@sentry/bundler-plugin-core index.mjs:1 import { transformAsync } from '@babel/core'
Because these are static re-exports rather than lazy imports, they are part of the module graph and get evaluated when the entry is evaluated — no call to sentrySvelteKit() required.
Measurement
Node v24.15.0, macOS, pnpm 11.16.0, 3 runs each. cjs counts require.cache keys after the dynamic import resolves:
| import |
time |
CJS modules loaded |
of which @babel/* |
@sentry/node (ESM entry) |
2.8 – 3.2 s |
168 |
0 |
@sentry/sveltekit (server entry) |
4.0 – 5.0 s |
392 |
201 |
Repro:
// probe.mjs, run with: node probe.mjs
import { createRequire } from 'node:module';
const require = createRequire(process.cwd() + '/');
await import('@sentry/sveltekit');
const loaded = Object.keys(require.cache);
console.log(loaded.length, loaded.filter((p) => p.includes('/@babel/')).length);
// -> 392 201
The absolute timings are from a laptop and won't transfer to a serverless runtime, but the module counts are deterministic.
Impact we observed
With @sveltejs/adapter-vercel, SvelteKit leaves @sentry/sveltekit unbundled in the server output — it survives as a single bare from "@sentry/sveltekit" import in the chunk our logger lives in, and the logger is imported by essentially every repository and service. @vercel/nft then traces that specifier and, because it traces files rather than symbols, copies the whole reachable graph into the serverless function:
- 156 packages / 2,743 files / 22 MB in a single
.func, including @babel/core, @babel/traverse, @babel/parser, @babel/generator, @babel/types, caniuse-lite (via babel's browserslist), acorn and @sentry/bundler-plugin-core
- those 201 Babel files are then loaded on every cold start, per the measurement above
Note this is the outcome @Lms24 suggested in #19360 as the mitigation ("use tools like @vercel/nft to prune your runtime node modules") — nft is already what runs here, and it can't help, because the reachability is real rather than incidental.
Suggested fix
Move sentrySvelteKit off the runtime entry, e.g. to a dedicated @sentry/sveltekit/vite export used from vite.config.ts. That keeps the one-package installation story intact (no new package to publish, no version-skew risk) while keeping the build plugin out of the runtime graph.
If a breaking change to the . export isn't desirable, exposing it via a lazy accessor rather than a static re-export would also break the static edge.
Environment
@sentry/sveltekit 10.69.0
@sveltejs/kit 2.70.2, @sveltejs/adapter-vercel 6.3.4, svelte 5.56.8, vite 8.2.0
- Node v24.15.0, pnpm 11.16.0
Summary
@sentry/sveltekit's server entry point statically re-exports the Vite build plugin, so importing the SDK in server code instantiates and evaluates the entire build-time module graph — including@babel/coreand ~200 of its files.This is deliberately not a request to move
@sentry/vite-pluginout ofdependencies— I've read #19360 and #18215 and understand the one-package decision. This is the narrower follow-up to what @Lms24 wrote in #19360:It is pulled in, and the mechanism is a single static re-export.
The chain
build/esm/index.server.js:2— the Node/defaultcondition entry:From there every link is a top-level static ESM import:
Because these are static re-exports rather than lazy imports, they are part of the module graph and get evaluated when the entry is evaluated — no call to
sentrySvelteKit()required.Measurement
Node v24.15.0, macOS, pnpm 11.16.0, 3 runs each.
cjscountsrequire.cachekeys after the dynamic import resolves:@babel/*@sentry/node(ESM entry)@sentry/sveltekit(server entry)Repro:
The absolute timings are from a laptop and won't transfer to a serverless runtime, but the module counts are deterministic.
Impact we observed
With
@sveltejs/adapter-vercel, SvelteKit leaves@sentry/sveltekitunbundled in the server output — it survives as a single barefrom "@sentry/sveltekit"import in the chunk our logger lives in, and the logger is imported by essentially every repository and service.@vercel/nftthen traces that specifier and, because it traces files rather than symbols, copies the whole reachable graph into the serverless function:.func, including@babel/core,@babel/traverse,@babel/parser,@babel/generator,@babel/types,caniuse-lite(via babel'sbrowserslist),acornand@sentry/bundler-plugin-coreNote this is the outcome @Lms24 suggested in #19360 as the mitigation ("use tools like
@vercel/nftto prune your runtime node modules") — nft is already what runs here, and it can't help, because the reachability is real rather than incidental.Suggested fix
Move
sentrySvelteKitoff the runtime entry, e.g. to a dedicated@sentry/sveltekit/viteexport used fromvite.config.ts. That keeps the one-package installation story intact (no new package to publish, no version-skew risk) while keeping the build plugin out of the runtime graph.If a breaking change to the
.export isn't desirable, exposing it via a lazy accessor rather than a static re-export would also break the static edge.Environment
@sentry/sveltekit10.69.0@sveltejs/kit2.70.2,@sveltejs/adapter-vercel6.3.4,svelte5.56.8,vite8.2.0