feat(browser): Report web vitals for soft navigations - #23425
Draft
logaretm wants to merge 9 commits into
Draft
Conversation
Contributor
size-limit report 📦
|
Report LCP, CLS and INP for soft navigations, using upstream web-vitals'
`reportSoftNavs` option and the browser's Soft Navigations API.
Soft navigation vitals are correlated back to the SDK's navigation spans
through the `interactionId` of the interaction that triggered the
navigation, which both the `soft-navigation` entry and the interaction's
own Event Timing entry carry.
Opt out via `webVitalsIntegration({ reportSoftNavs: false })` or
`browserTracingIntegration({ enableSoftNavWebVitals: false })`.
Drop `reportAllChanges` when soft navigations are on, so every value a handler receives is already final for its navigation and can be sent as a span directly. Replaces the accumulate/flush state machine, and leaves the default path untouched.
Soft navigations the browser doesn't detect (programmatic navigations, navigations that never paint) report no vitals at all, so coverage is lower than for page loads. That is worth stating next to the option rather than leaving it to be discovered from missing data. Also covers that `browserTracingIntegration` forwards the option to the `webVitalsIntegration` it auto-registers.
INP took its parent from the interaction cache, which records whichever root span was active when the interaction's entry was observed. For a click that drives a navigation that is the destination navigation span, so the page load's INP was reported against the first navigation. With soft navigations the metric carries the navigation it belongs to, so INP now flows through the same path as LCP and CLS. The cache is still used for the element name, and still parents INP when soft navigations are off.
web-vitals decides what an INP is; we map its emissions to telemetry rather than filtering them. It reports a soft navigation's INP with no entries when every interaction stayed below the Event Timing threshold, and we were dropping those, so a fast navigation reported no INP at all. Without an entry there is no element or interaction type to describe, so the span falls back to a bare `ui.interaction` op and a generic name, the same way CLS falls back to a "Layout shift" span. It is placed at the start of the navigation it belongs to.
web-vitals synthesizes an INP value with no entries when a soft navigation's interactions all stayed below the Event Timing threshold, so that fast navigations are not excluded from INP reporting (GoogleChrome/web-vitals#724). Emitting those under a bare `ui.interaction` op would hide them from any aggregation matching `ui.interaction.*`, reintroducing exactly the bias upstream added the synthetic value to remove. Without an entry there is no interaction type to derive, so the op falls back to `click`.
A page's vitals are only comparable across routes if every route reports
its own. Behind an opt-in, the default stayed a single page-lifetime LCP,
CLS and INP that implicitly belong to whichever route happened to load
first, which is the less useful of the two behaviours in an app that soft
navigates at all.
Soft navigation vitals are now on by default and configured through a
nested `webVitals` bag on `browserTracingIntegration`, typed as the
`webVitalsIntegration` options it forwards to, so the two ways of
configuring the same integration cannot drift:
browserTracingIntegration({ webVitals: { softNavigations: false } })
webVitalsIntegration({ softNavigations: false })
The keys are positive with per-key defaults rather than `disable*` flags,
because the defaults in this bag point in opposite directions: soft
navigations are reported by default, while bfcache re-reports are dropped
(see `withoutBfcache`). A negated scheme would have to spell an
off-by-default feature as `disableBfcache: true` in the defaults.
`enableInp` is deprecated in favour of `webVitals: { ignore: ['inp'] }`,
which is the same switch spelled twice. It keeps working and is merged
into any user-provided ignore list.
Soft navigation vitals are still ignored when span streaming is off or
the browser has no Soft Navigations API, so nothing changes for a browser
that can't detect soft navigations in the first place.
logaretm
force-pushed
the
awad/js-1019-soft-nav-web-vitals-spans
branch
from
August 28, 2026 15:28
7105e45 to
0d9c2e6
Compare
…red on Adds `browser.navigation.type` to every emitted LCP, CLS and INP span, so a vital can be read against the kind of navigation that produced it. A soft navigation and a cold page load are not comparable measurements, and without this there is no way to separate them after the fact. web-vitals reports a wider set of navigation types than the attribute defines, so only the states Navigation Timing cannot express keep their own value. Every ordinary document navigation folds into `navigate`, including a back/forward that missed the bfcache (per the spec) and a `document.wasDiscarded` restore, which the spec does not name at all. The `back-forward-cache` -> `bfcache` mapping is included but unreachable today: `withoutBfcache` drops those metrics before they reach a span. It's here so that enabling bfcache vitals later doesn't silently report them as `navigate`. Spec: getsentry/sentry-conventions#600
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reports LCP, CLS and INP for soft navigations, opt-in via
webVitalsIntegration({ reportSoftNavs: true }). Correlates them to navigation spans via the triggeringinteractionId, sincenavigationIdisn't available at history-change time. Requires span streaming.Verified in Chrome 151, which surfaced two pre-existing attribution bugs, fixed here. One changes the default path: vital spans read the route name from the current scope, so a page load's LCP and CLS were labelled with the next route.
One interesting case is web-vitals synthesizes an INP with no entries so fast soft navs still report (GoogleChrome/web-vitals#724). That leaves no interaction type, and a bare op would hide those spans from aggregation. So it falls back to
click.closes #17857