Skip to content

Commit c84d055

Browse files
committed
feat: update fcp
1 parent 013b9a9 commit c84d055

File tree

1 file changed

+59
-8
lines changed
  • packages/browser-utils/src/metrics/web-vitals

1 file changed

+59
-8
lines changed

packages/browser-utils/src/metrics/web-vitals/onFCP.ts

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616

1717
import { bindReporter } from './lib/bindReporter';
1818
import { getActivationStart } from './lib/getActivationStart';
19+
import { getNavigationEntry } from './lib/getNavigationEntry';
1920
import { getVisibilityWatcher } from './lib/getVisibilityWatcher';
2021
import { initMetric } from './lib/initMetric';
2122
import { observe } from './lib/observe';
23+
import { getSoftNavigationEntry, softNavs } from './lib/softNavs';
2224
import { whenActivated } from './lib/whenActivated';
23-
import type { FCPMetric, MetricRatingThresholds, ReportOpts } from './types';
25+
import type { FCPMetric, Metric, MetricRatingThresholds, ReportOpts } from './types';
2426

2527
/** Thresholds for FCP. See https://web.dev/articles/fcp#what_is_a_good_fcp_score */
2628
export const FCPThresholds: MetricRatingThresholds = [1800, 3000];
@@ -32,31 +34,80 @@ export const FCPThresholds: MetricRatingThresholds = [1800, 3000];
3234
* value is a `DOMHighResTimeStamp`.
3335
*/
3436
export const onFCP = (onReport: (metric: FCPMetric) => void, opts: ReportOpts = {}) => {
37+
// Set defaults
38+
const softNavsEnabled = softNavs(opts);
39+
let metricNavStartTime = 0;
40+
const hardNavId = getNavigationEntry()?.navigationId || '1';
41+
3542
whenActivated(() => {
36-
const visibilityWatcher = getVisibilityWatcher();
37-
const metric = initMetric('FCP');
43+
let visibilityWatcher = getVisibilityWatcher();
44+
let metric = initMetric('FCP');
3845
let report: ReturnType<typeof bindReporter>;
3946

47+
const initNewFCPMetric = (navigation?: Metric['navigationType'], navigationId?: string) => {
48+
metric = initMetric('FCP', 0, navigation, navigationId);
49+
report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges);
50+
if (navigation === 'soft-navigation') {
51+
visibilityWatcher = getVisibilityWatcher(true);
52+
const softNavEntry = navigationId ? getSoftNavigationEntry(navigationId) : null;
53+
metricNavStartTime = softNavEntry ? softNavEntry.startTime || 0 : 0;
54+
}
55+
};
56+
4057
const handleEntries = (entries: FCPMetric['entries']) => {
4158
for (const entry of entries) {
4259
if (entry.name === 'first-contentful-paint') {
43-
po!.disconnect();
60+
if (!softNavsEnabled) {
61+
// If we're not using soft navs monitoring, we should not see
62+
// any more FCPs so can disconnect the performance observer
63+
po!.disconnect();
64+
} else if (entry.navigationId && entry.navigationId !== metric.navigationId) {
65+
// If the entry is for a new navigationId than previous, then we have
66+
// entered a new soft nav, so reinitialize the metric.
67+
initNewFCPMetric('soft-navigation', entry.navigationId);
68+
}
69+
70+
let value = 0;
4471

45-
// Only report if the page wasn't hidden prior to the first paint.
46-
if (entry.startTime < visibilityWatcher.firstHiddenTime) {
72+
if (!entry.navigationId || entry.navigationId === hardNavId) {
73+
// Only report if the page wasn't hidden prior to the first paint.
4774
// The activationStart reference is used because FCP should be
4875
// relative to page activation rather than navigation start if the
4976
// page was prerendered. But in cases where `activationStart` occurs
5077
// after the FCP, this time should be clamped at 0.
51-
metric.value = Math.max(entry.startTime - getActivationStart(), 0);
78+
value = Math.max(entry.startTime - getActivationStart(), 0);
79+
} else {
80+
const softNavEntry = getSoftNavigationEntry(entry.navigationId);
81+
const softNavStartTime = softNavEntry?.startTime ?? 0;
82+
// As a soft nav needs an interaction, it should never be before
83+
// getActivationStart so can just cap to 0
84+
value = Math.max(entry.startTime - softNavStartTime, 0);
85+
}
86+
87+
// Only report if the page wasn't hidden prior to FCP.
88+
// Or it's a soft nav FCP
89+
const softNavEntry =
90+
softNavsEnabled && entry.navigationId ? getSoftNavigationEntry(entry.navigationId) : null;
91+
const softNavEntryStartTime = softNavEntry?.startTime ?? 0;
92+
if (
93+
entry.startTime < visibilityWatcher.firstHiddenTime ||
94+
(softNavsEnabled &&
95+
entry.navigationId &&
96+
entry.navigationId !== metric.navigationId &&
97+
entry.navigationId !== hardNavId &&
98+
softNavEntryStartTime > metricNavStartTime)
99+
) {
100+
metric.value = value;
52101
metric.entries.push(entry);
102+
metric.navigationId = entry.navigationId || '1';
103+
// FCP should only be reported once so can report right
53104
report(true);
54105
}
55106
}
56107
}
57108
};
58109

59-
const po = observe('paint', handleEntries);
110+
const po = observe('paint', handleEntries, opts);
60111

61112
if (po) {
62113
report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges);

0 commit comments

Comments
 (0)