Skip to content

feat(browser): Experimental soft navigation web vitals support - #22880

Closed
logaretm wants to merge 8 commits into
developfrom
awad/js-1019-webvitals-softnavs
Closed

feat(browser): Experimental soft navigation web vitals support#22880
logaretm wants to merge 8 commits into
developfrom
awad/js-1019-webvitals-softnavs

Conversation

@logaretm

@logaretm logaretm commented Jul 30, 2026

Copy link
Copy Markdown
Member

This PR implements soft navigation support which is pulled from GoogleChrome/web-vitals#308 which entered recently origin trials.

Still need to check how this affects product and how to incorporate the way it works currently with our UI and if that's needed at all.

Links:

How it works

Two ways to turn it on.

Via the tracing integration (forwards the flag when it auto-registers webVitalsIntegration):

Sentry.init({
  integrations: [
    Sentry.browserTracingIntegration({
      _experiments: { enableSoftNavWebVitals: true },
    }),
  ],
});

Or directly on the web vitals integration:

Sentry.init({
  integrations: [Sentry.webVitalsIntegration({ reportSoftNavs: true })],
});

Once enabled, reportSoftNavs flows into the vendored web-vitals lib, which re-initializes each metric on a new navigationId so LCP/CLS/INP/TTFB/FCP are also captured for soft navigations. Those soft-nav vitals are emitted as v2 spans that group with the navigation span by trace id.

closes #17857

@linear-code

linear-code Bot commented Jul 30, 2026

Copy link
Copy Markdown

JS-1019

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 29.83 kB - -
@sentry/browser - with treeshaking flags 28.03 kB - -
@sentry/browser (incl. Tracing) 49.02 kB +3.77% +1.78 kB 🔺
@sentry/browser (incl. Tracing + Span Streaming) 49.05 kB +3.77% +1.78 kB 🔺
@sentry/browser (incl. Tracing, Profiling) 53.83 kB +3.53% +1.83 kB 🔺
@sentry/browser (incl. Tracing, Replay) 88.35 kB +2.04% +1.76 kB 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 77.57 kB +2.07% +1.57 kB 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 93.05 kB +1.95% +1.77 kB 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 105.73 kB +1.72% +1.78 kB 🔺
@sentry/browser (incl. Feedback) 47.13 kB - -
@sentry/browser (incl. sendFeedback) 34.67 kB - -
@sentry/browser (incl. FeedbackAsync) 39.78 kB - -
@sentry/browser (incl. Metrics) 30.91 kB - -
@sentry/browser (incl. Logs) 31.14 kB - -
@sentry/browser (incl. Metrics & Logs) 31.81 kB - -
@sentry/react 31.63 kB - -
@sentry/react (incl. Tracing) 51.2 kB +3.42% +1.69 kB 🔺
@sentry/vue 34.92 kB - -
@sentry/vue (incl. Tracing) 50.95 kB +3.48% +1.71 kB 🔺
@sentry/svelte 29.86 kB - -
CDN Bundle 31.87 kB - -
CDN Bundle (incl. Tracing) 49.28 kB +3.68% +1.75 kB 🔺
CDN Bundle (incl. Logs, Metrics) 33.49 kB +0.24% +79 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 50.61 kB +3.4% +1.66 kB 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 73.78 kB +1.42% +1.03 kB 🔺
CDN Bundle (incl. Tracing, Replay) 86.84 kB +1.95% +1.66 kB 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 88.09 kB +1.85% +1.59 kB 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 92.67 kB +1.82% +1.66 kB 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 93.96 kB +1.79% +1.65 kB 🔺
CDN Bundle - uncompressed 94.62 kB - -
CDN Bundle (incl. Tracing) - uncompressed 147.9 kB +4.07% +5.78 kB 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 99.48 kB +0.25% +239 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 151.88 kB +3.96% +5.78 kB 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 227.35 kB +1.53% +3.41 kB 🔺
⛔️ CDN Bundle (incl. Tracing, Replay) - uncompressed (max: 267 kB) 267.15 kB +2.21% +5.77 kB 🔺
⛔️ CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed (max: 271 kB) 271.11 kB +2.18% +5.77 kB 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 280.85 kB +2.1% +5.77 kB 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 284.81 kB +2.07% +5.77 kB 🔺
@sentry/nextjs (client) 53.76 kB +3.23% +1.68 kB 🔺
@sentry/sveltekit (client) 49.44 kB +3.62% +1.73 kB 🔺
@sentry/core/server 65.52 kB - -
@sentry/core/browser 51.88 kB - -
@sentry/node 120.24 kB +0.01% +1 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 0 B added added
@sentry/node - without tracing 83.69 kB +0.01% +1 B 🔺
@sentry/aws-serverless 92.35 kB -0.01% -1 B 🔽
@sentry/cloudflare (withSentry) - minified 218.65 kB - -
@sentry/cloudflare (withSentry) 538.85 kB - -

View base workflow run

@logaretm
logaretm force-pushed the awad/js-1019-webvitals-softnavs branch from 3a26d29 to 42b6fa6 Compare August 4, 2026 20:17
Re-port of Chrome Soft Navigation web-vitals support onto current develop.
Adds an opt-in _experiments.enableSoftNavWebVitals flag on
browserTracingIntegration that threads reportSoftNavs through
webVitalsIntegration into the vendored web-vitals library and the tracking
functions. Soft nav vitals are emitted as v2 spans grouped with the
navigation span by trace ID; pageload vitals are unchanged.

The vendored library carries the soft-nav port of GoogleChrome/web-vitals#308.
Upstream shipped it in v6.0.0 entangled with BFCache handling this fork
intentionally removed, so it was adapted onto our BFCache-free v5.1.0 base.
Best-effort correlation between Chrome Soft Navigation navigationIds and the
Sentry navigation span they belong to. Navigation spans are registered as they
start; when the async soft-navigation performance entry lands it is matched back
to the span by start time (drift measured at ~1-2ms) and the navigationId is
stamped onto the span. Soft nav web vital spans then look up their navigation
span by navigationId and link via sentry.navigation.span_id.

Soft nav web vital spans are tagged with a dedicated origin
(auto.http.browser.soft_navigation) and a sentry.web_vital.navigation_type
attribute so consumers can identify them. Correlation is inherently partial
(programmatic/non-painting navs produce no entry); the vital span is always
emitted, with the navigation link added only when correlation succeeds.
Three fixes for soft navigation web vitals, found by comparing against upstream
web-vitals@6 running in parallel as a reference oracle:

- INP: thread navigationId into the emitted span so soft-nav INP is tagged with
  the soft_navigation origin + sentry.web_vital.navigation_type and correlated to
  its navigation span, instead of emitting as an untagged ui.interaction.click.
- LCP/CLS: a soft-nav value of 0 is a valid data point (the contentful paint often
  lands at navigation start; a nav with no layout shift has CLS 0). Previously these
  were dropped by the pageload-path validity/entry guards, so no LCP/CLS spans were
  emitted for soft navs at all. Emit them for soft navs.
- getLCP: guard against undefined entries in the handler loop, which crashed with
  'Cannot read properties of undefined (reading startTime)' and aborted LCP
  reporting (upstream web-vitals#725).
The vendored web-vitals INP collector can report the same finalized value more
than once around a soft-navigation boundary (once on the interaction, then again
when the next soft nav re-initializes the metric). Track the last emitted value
per navigation and skip duplicate reports so a single INP span is emitted per
soft navigation.
This reverts commit d80ee6c89b3e936d048654e5a9c9d2f6b69219be.
Match a soft-navigation entry to the navigation span it belongs to by the
interactionId of the interaction that triggered it, rather than by start-time
proximity. The triggering interaction's PerformanceEventTiming entry and the
resulting soft-navigation entry share the same interactionId, so the join is
deterministic instead of a timing-window heuristic.
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.

Investigate and Experiment with soft navigation web vitals

1 participant