feat(plugin-web-vitals-browser): add reportSoftNav option for soft navigation reporting - #1965
Draft
daniel-graham-amplitude wants to merge 2 commits into
Draft
feat(plugin-web-vitals-browser): add reportSoftNav option for soft navigation reporting#1965daniel-graham-amplitude wants to merge 2 commits into
daniel-graham-amplitude wants to merge 2 commits into
Conversation
…vigation reporting Adds `WebVitalsOptions` with a `reportSoftNav` flag, wired through `autocapture.webVitals`, which now accepts an options object as well as a boolean. When enabled, the plugin opts in to web-vitals' `reportSoftNavs` and collects LCP/FCP/INP/CLS/TTFB per navigation rather than only for the initial page load, sending one `[Amplitude] Web Vitals` event per navigation with the page properties of the URL its metrics belong to. Requires bumping web-vitals from 5.1.0 to 6.2.1, which is where soft navigation support landed.
size-limit report 📦
|
daniel-graham-amplitude
force-pushed
the
feat/web-vitals-soft-nav
branch
from
September 2, 2026 18:35
ad1921c to
9a23983
Compare
Collaborator
Author
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Missing navigationId on metric payload
- processMetric now accepts reportSoftNavs and writes navigationId onto the metric payload only when that flag is on, matching the documented event shape.
Or push these changes by commenting:
@cursor push d4418ea72b
Preview (d4418ea72b)
diff --git a/packages/plugin-web-vitals-browser/src/web-vitals-plugin.ts b/packages/plugin-web-vitals-browser/src/web-vitals-plugin.ts
--- a/packages/plugin-web-vitals-browser/src/web-vitals-plugin.ts
+++ b/packages/plugin-web-vitals-browser/src/web-vitals-plugin.ts
@@ -21,6 +21,7 @@
id: string;
timestamp: number;
navigationStart: number;
+ navigationId?: Metric['navigationId'];
};
type WebVitalsMetricProperty =
@@ -60,7 +61,7 @@
return performance.timeOrigin + startTime;
}
-function processMetric(metric: Metric): WebVitalsMetricPayload {
+function processMetric(metric: Metric, reportSoftNavs: boolean): WebVitalsMetricPayload {
return {
value: metric.value,
rating: metric.rating,
@@ -71,6 +72,8 @@
// A soft navigation's metrics are measured from the start of that navigation rather than from
// the document's time origin. `navigationStartTime` is 0 for the initial page load.
navigationStart: Math.floor(performance.timeOrigin + /* istanbul ignore next */ (metric.navigationStartTime || 0)),
+ // Only included when reporting soft navigations so the default event stays byte-identical.
+ ...(reportSoftNavs && { navigationId: metric.navigationId }),
};
}
diff --git a/packages/plugin-web-vitals-browser/test/web-vitals-plugin.test.ts b/packages/plugin-web-vitals-browser/test/web-vitals-plugin.test.ts
--- a/packages/plugin-web-vitals-browser/test/web-vitals-plugin.test.ts
+++ b/packages/plugin-web-vitals-browser/test/web-vitals-plugin.test.ts
@@ -202,6 +202,7 @@
expect(eventObject['[Amplitude] INP']).toMatchObject(expectedMetric);
expect(eventObject['[Amplitude] CLS']).toMatchObject(expectedMetric);
expect(eventObject['[Amplitude] TTFB']).toMatchObject(expectedMetric);
+ expect(eventObject['[Amplitude] LCP']).not.toHaveProperty('navigationId');
expect(eventObject).toMatchObject({
'[Amplitude] Page Domain': 'www.example.com',
@@ -297,6 +298,7 @@
navigationType: 'soft-navigation',
// performance.timeOrigin (1000) + navigationStartTime (500)
navigationStart: 1500,
+ navigationId: 4,
});
});You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 9a23983. Configure here.
| // the document's time origin. `navigationStartTime` is 0 for the initial page load. | ||
| navigationStart: Math.floor(performance.timeOrigin + /* istanbul ignore next */ (metric.navigationStartTime || 0)), | ||
| }; | ||
| } |
There was a problem hiding this comment.
Missing navigationId on metric payload
Medium Severity
processMetric is called with reportSoftNavs, but the function only accepts the metric and never writes navigationId onto WebVitalsMetricPayload. Soft-nav events therefore omit the per-navigation identifier this change describes as present when reportSoftNav is on.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 9a23983. Configure here.
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.



Summary
Adds a
reportSoftNavoption to a newWebVitalsOptionstype, and reports web vitals per soft navigation when it is enabled.autocapture.webVitalsnow accepts an options object in addition to a boolean:and the plugin takes the same options directly:
webVitalsPlugin({ reportSoftNav: true }).Default behavior is unchanged.
reportSoftNavdefaults tofalse, andwebVitals: truekeeps producing exactly the same single[Amplitude] Web Vitalsevent with the same properties as before.What changes when it's on
The plugin passes
reportSoftNavs: trueto each ofonLCP/onFCP/onINP/onCLS/onTTFB, so web-vitals measures LCP, FCP, INP, CLS and TTFB per soft navigation instead of only for the initial page load. The plugin then:metric.navigationIdand sends one[Amplitude] Web Vitalsevent per navigation.metric.navigationURLrather than the current URL — a navigation's metrics can be reported after the next navigation has already begun, so the current URL is the wrong one.navigationStartrelative to the navigation being measured (performance.timeOrigin + metric.navigationStartTime) and includesnavigationIdon each metric. Metrics for a soft navigation carrynavigationType: 'soft-navigation', so they're distinguishable downstream.SOFT_NAV_FLUSH_DELAY_MS) so metrics finalized just after the navigation boundary still land in the right event. The current navigation is still sent onvisibilitychange→hidden.visibilitychangelistener installed (today it removes itself after firing once), since the page can be hidden and shown again with more navigations to report. Events with no metrics are not sent.In browsers without the Soft Navigations API, every metric shares the hard navigation's
navigationId, so behavior falls back to a single event — same as today.Dependency bump
web-vitals5.1.0 → 6.2.1. Soft navigation support (ReportOpts.reportSoftNavs,Metric.navigationId/navigationURL/navigationStartTime) landed in v6; there is no way to do this on 5.x. Same package layout and same call signatures for the fiveonXfunctions we use.One wrinkle worth a look: web-vitals@6's Soft Navigations types reference DOM types that don't exist in the TypeScript 4.9 DOM lib (
NavigationType), soskipLibCheckis set for this package (there's precedent in the react-native packages). The alternative is a repo-wide TypeScript upgrade, which felt out of scope here.Open questions for review
visibilitychange. Happy to change it.navigationIdon the metric payload. Currently only added whenreportSoftNavis on, to keep the default event shape byte-identical. Worth always including?AutocaptureOptionsRemoteConfig extends AutocaptureOptions, so the object form flows through remote config without extra work. No transform was added — let me know if this should be gated differently server-side.Testing
plugin-web-vitals-browser: 20 tests, 100% statements/branches/functions/lines. Covers the unchanged default path, opting in per metric, per-navigation events, late-arriving metrics for a superseded navigation, several soft navigations in quick succession, repeated hides, teardown with a pending flush, and unparseable navigation URLs.analytics-browser: added coverage forisWebVitalsEnabledwith the object form and for the newgetWebVitalsConfig, plus abrowser-clienttest asserting the options reach the plugin. Full suite passes at 100% coverage.analytics-core: full suite passes at 100% coverage.test-server/browser-sdk/web-vitals-soft-nav.html, which pushes history entries and paints new content so soft navigations are actually detected. Needs Chromium 151+ to exercise the new path — I have not yet run it against a browser with Soft Navigations support, which is part of why this is a draft.Checklist
autocapture.webVitalswidens frombooleantoboolean | WebVitalsOptions, and the default event is unchanged. Theweb-vitalsmajor bump is an internal dependency of the plugin.Note
Medium Risk
Default web vitals behavior is unchanged, but the plugin’s event timing/shape changes when
reportSoftNavis enabled and it depends on a majorweb-vitalsbump.Overview
Adds
WebVitalsOptions(starting withreportSoftNav) and widensautocapture.webVitalstoboolean | WebVitalsOptions. The browser SDK forwards object config viagetWebVitalsConfigintowebVitalsPlugin(options);webVitals: truestill passes no options and behavior stays the same.With
reportSoftNav: true, the plugin upgradesweb-vitalsto v6, opts into soft-navigation measurement, buckets metrics by navigation, attaches page properties fromnavigationURL, and emits one[Amplitude] Web Vitalsevent per navigation (superseded navigations flush after a 1s delay; the active navigation still flushes on hide). The visibility listener stays registered for repeated hide/show cycles when soft nav is on.Docs, unit/integration tests, and a manual
web-vitals-soft-nav.htmltest page are included; the plugin enablesskipLibCheckfor v6 DOM types on TS 4.9.Reviewed by Cursor Bugbot for commit 9a23983. Bugbot is set up for automated code reviews on this repo. Configure here.