Skip to content

ref(browser): Tree-shake span streaming out of error-only bundles - #23362

Draft
Lms24 wants to merge 1 commit into
lms/ref-core-browser-span-apisfrom
lms/ref-browser-treeshake-span-streaming
Draft

ref(browser): Tree-shake span streaming out of error-only bundles#23362
Lms24 wants to merge 1 commit into
lms/ref-core-browser-span-apisfrom
lms/ref-browser-treeshake-span-streaming

Conversation

@Lms24

@Lms24 Lms24 commented Aug 12, 2026

Copy link
Copy Markdown
Member

Stacked on #23361. Makes spanStreamingIntegration reachable only if span-creating code is reachable, so error-only apps drop the whole span streaming graph automatically — no __SENTRY_TRACING__ flag needed. No public API changes: Sentry.startSpan() from @sentry/browser works exactly as before.

Measured with size-limit (webpack, gzipped + minified):

app imports before after
init 30.30 kB 28.59 kB
init + treeshaking flags 28.52 kB 26.95 kB
floor: init + __SENTRY_TRACING__: false 26.85 kB 26.85 kB
init, browserTracingIntegration 48.69 kB 48.56 kB

Landing within 0.10 kB of the __SENTRY_TRACING__: false floor shows init's reference was the only thing retaining span streaming — there is no second reference to hunt down. Tracing users pay nothing extra.

This PR:

  • Removes the spanStreamingIntegration() push from packages/browser/src/sdk.ts, along with the now-unused __SENTRY_TRACING__ declaration.
  • Switches browser-facing packages to the @sentry/core/browser span-start APIs: browser-utils (performance/utils.ts, web-vitals/spans.ts), browser/src/integrations/fetchStreamPerformance.ts, svelte, sveltekit/src/client/load.ts, react-router/src/client/createClientInstrumentation.ts and effect/src/tracer.ts. Import names are unchanged.
  • Calls _INTERNAL_ensureBrowserSpanStreaming(client) from browserTracingIntegration's afterAllSetup. Not load-bearing for correctness, but it means getIntegrationByName('SpanStreaming') and event.sdk.integrations are correct for errors captured before the first span starts.
  • Drops the nextSpan re-export from packages/nextjs/src/client/index.ts, so the client keeps @sentry/react's span APIs.
  • Adds the sdk/no-unguarded-span-apis oxlint rule, enabled once in .oxlintrc.base.json.
  • Tightens the three init-only .size-limit.js entries to under 1 KB headroom.
  • Adds packages/browser/test/tracing/spanStreamingWiring.test.ts, covering the wiring through init end-to-end.

Notes for review:

  • Client.addIntegration runs setup() synchronously and is idempotent by name, so the afterSpanEnd listener is always registered before any span can end: a span can only end if it was started, and starting goes through a wrapper.
  • The ensure call is in afterAllSetup, not setup. Client._setupIntegrations assigns this._integrations = setupIntegrations(...) after the setup loop, so an addIntegration from inside another integration's setup() gets clobbered in the index — the hooks would still fire, but the bookkeeping this step exists for would be lost.
  • The lint rule is the only enforcement. Both variants share their names, so a stray plain import compiles fine and just stops sending spans. It has to land with the code it protects. It caught react-router/src/client/createClientInstrumentation.ts, which the original audit missed.
  • effect/src/tracer.ts backs both the client and the server entry, so it uses the guarded variant too. On the server that's a no-op: ServerRuntimeClient already installs the integration eagerly and addIntegration dedupes by name. This is also why the rule's scope is expressed in code rather than as config globs — no files glob can say "this one file but not its siblings".
  • The Cache Components overrides in nextjs/src/common/utils/nextSpan.ts are unreachable in the browser: isBuild() reads the server-only NEXT_PHASE env var, isUseCacheFunction() is a React server-reference check, and the browser export condition maps only to index.client.js (client components are SSR'd through the server entry). So the client needs no wrapper, and isBuild/isUseCacheFunction/SentryNonRecordingSpan drop out of the client bundle.
  • Rejected alternatives: guarding core's createChildOrRootSpan (same size win, one call site — but it installs core's variant, which lacks the browser variant's 500 ms per-trace flush on afterSegmentSpanEnd, meaning real data loss on navigation and tab close; it also makes core's hottest path depend on more of the Client surface and charges Node a per-span cost for a browser-only benefit). Registering an AsyncContextStrategy from init() defeats its own purpose: the registration references the browser startSpan, so nothing tree-shakes.

`init()` no longer pushes `spanStreamingIntegration()`. That single
reference was the only thing retaining `SpanBuffer`, `captureSpan`,
`estimateSize` and `scopeContextAttributes` in every browser bundle,
including bundles for apps that never use tracing.

Instead, browser-facing packages import their span-start APIs from
`@sentry/core/browser`, which installs the integration on the client
before the first span starts. A span can only end if it was started, so
the `afterSpanEnd` listener is always registered in time.

`init` alone drops from 28.52 kB to 26.95 kB gzipped, within 0.1 kB of
the `__SENTRY_TRACING__: false` floor. Tracing users pay nothing extra.

Both variants share their names, so a stray plain import compiles fine
and silently stops sending spans. `sdk/no-unguarded-span-apis` is the
only thing that catches that, hence it lands here rather than later.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 28.59 kB added added
@sentry/browser - with treeshaking flags 26.95 kB added added
@sentry/browser - with treeshaking flags tracing without tracing 26.85 kB added added
@sentry/browser (incl. Tracing) 48.56 kB added added
@sentry/browser (incl. Tracing + Span Streaming) 48.57 kB added added
@sentry/browser (incl. Tracing, Profiling) 51.51 kB added added
@sentry/browser (incl. Tracing, Replay) 87.95 kB added added
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 77.32 kB added added
@sentry/browser (incl. Tracing, Replay with Canvas) 92.67 kB added added
@sentry/browser (incl. Tracing, Replay, Feedback) 105.34 kB added added
@sentry/browser (incl. Feedback) 45.82 kB added added
@sentry/browser (incl. sendFeedback) 33.39 kB added added
@sentry/browser (incl. FeedbackAsync) 38.49 kB added added
@sentry/browser (incl. Metrics) 29.59 kB added added
@sentry/browser (incl. Logs) 29.86 kB added added
@sentry/browser (incl. Metrics & Logs) 30.55 kB added added
@sentry/react 30.37 kB added added
@sentry/react (incl. Tracing) 50.81 kB added added
@sentry/vue 35.39 kB added added
@sentry/vue (incl. Tracing) 50.53 kB added added
@sentry/svelte 28.62 kB added added
CDN Bundle 30.34 kB added added
CDN Bundle (incl. Tracing) 48.99 kB added added
CDN Bundle (incl. Logs, Metrics) 32.58 kB added added
CDN Bundle (incl. Tracing, Logs, Metrics) 50.91 kB added added
CDN Bundle (incl. Replay, Logs, Metrics) 72.98 kB added added
CDN Bundle (incl. Tracing, Replay) 86.57 kB added added
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 88.44 kB added added
CDN Bundle (incl. Tracing, Replay, Feedback) 92.26 kB added added
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 94.23 kB added added
CDN Bundle - uncompressed 89.98 kB added added
CDN Bundle (incl. Tracing) - uncompressed 146.85 kB added added
CDN Bundle (incl. Logs, Metrics) - uncompressed 96.38 kB added added
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 152.64 kB added added
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 225.33 kB added added
CDN Bundle (incl. Tracing, Replay) - uncompressed 266.11 kB added added
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 271.89 kB added added
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 279.8 kB added added
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 285.57 kB added added
@sentry/nextjs (client) 53.3 kB added added
@sentry/sveltekit (client) 49 kB added added
@sentry/core/server 65.45 kB added added
@sentry/core/browser 52.05 kB added added
@sentry/node 117.97 kB added added
@sentry/node/import (ESM hook with diagnostics-channel injection) 0 B added added
@sentry/node - without tracing 82.09 kB added added
@sentry/aws-serverless 91.5 kB added added
@sentry/cloudflare (withSentry) - minified 214.07 kB added added
@sentry/cloudflare (withSentry) 528.89 kB added added

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant