From 90c516b2a944f07c15f27256e76ada84abc71c38 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 1 Feb 2022 21:48:08 +0100 Subject: [PATCH] Flush buffered vitals metrics on page mount (#33867) ## Bug fixes #32631 - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [x] Errors have helpful link attached, see `contributing.md` --- packages/next/client/index.tsx | 5 ++++- packages/next/client/vitals.ts | 19 +++++++++++++++++++ .../relay-analytics-disabled/pages/index.js | 3 +++ .../test/index.test.js | 2 ++ .../relay-analytics/pages/index.js | 2 ++ .../relay-analytics/test/index.test.js | 2 ++ 6 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/next/client/index.tsx b/packages/next/client/index.tsx index 2fce72ecb5a8389..3e7264a9663bb02 100644 --- a/packages/next/client/index.tsx +++ b/packages/next/client/index.tsx @@ -33,7 +33,7 @@ import measureWebVitals from './performance-relayer' import { RouteAnnouncer } from './route-announcer' import { createRouter, makePublicRouterInstance } from './router' import { getProperError } from '../lib/is-error' -import { trackWebVitalMetric } from './vitals' +import { flushBufferedVitalsMetrics, trackWebVitalMetric } from './vitals' import { RefreshContext } from './rsc/refresh' /// @@ -1010,7 +1010,10 @@ function Root({ // don't cause any hydration delay: React.useEffect(() => { measureWebVitals(onPerfEntry) + + flushBufferedVitalsMetrics() }, []) + return children as React.ReactElement } diff --git a/packages/next/client/vitals.ts b/packages/next/client/vitals.ts index 0f26bea7960184b..32029868674d50f 100644 --- a/packages/next/client/vitals.ts +++ b/packages/next/client/vitals.ts @@ -3,8 +3,19 @@ import { NextWebVitalsMetric } from '../pages/_app' type ReportWebVitalsCallback = (webVitals: NextWebVitalsMetric) => any export const webVitalsCallbacks = new Set() + +let flushed = false const metrics: NextWebVitalsMetric[] = [] +export function getBufferedVitalsMetrics() { + return metrics +} + +export function flushBufferedVitalsMetrics() { + flushed = true + metrics.length = 0 +} + export function trackWebVitalMetric(metric: NextWebVitalsMetric) { metrics.push(metric) webVitalsCallbacks.forEach((callback) => callback(metric)) @@ -13,6 +24,14 @@ export function trackWebVitalMetric(metric: NextWebVitalsMetric) { export function useWebVitalsReport(callback: ReportWebVitalsCallback) { const metricIndexRef = useRef(0) + if (process.env.NODE_ENV === 'development') { + if (flushed) { + console.error( + 'The `useWebVitalsReport` hook was called too late -- did you use it inside of a boundary?' + ) + } + } + useEffect(() => { // Flush calculated metrics const reportMetric = (metric: NextWebVitalsMetric) => { diff --git a/test/integration/relay-analytics-disabled/pages/index.js b/test/integration/relay-analytics-disabled/pages/index.js index 46c828a80fd0d44..b5832179bbd8470 100644 --- a/test/integration/relay-analytics-disabled/pages/index.js +++ b/test/integration/relay-analytics-disabled/pages/index.js @@ -1,3 +1,5 @@ +import { getBufferedVitalsMetrics } from 'next/dist/client/vitals' + if (typeof navigator !== 'undefined') { window.__BEACONS = window.__BEACONS || [] @@ -20,6 +22,7 @@ export default () => {

Foo!

bar!

+

{`buffered metrics: ${getBufferedVitalsMetrics().length}`}

) } diff --git a/test/integration/relay-analytics-disabled/test/index.test.js b/test/integration/relay-analytics-disabled/test/index.test.js index f7dc28e647999bb..0994f67ddb1511c 100644 --- a/test/integration/relay-analytics-disabled/test/index.test.js +++ b/test/integration/relay-analytics-disabled/test/index.test.js @@ -30,11 +30,13 @@ describe('Analytics relayer (disabled)', () => { const browser = await webdriver(appPort, '/') await browser.waitForElementByCss('h1') const h1Text = await browser.elementByCss('h1').text() + const pText = await browser.elementByCss('p').text() const firstContentfulPaint = parseFloat( await browser.eval('localStorage.getItem("FCP")') ) expect(h1Text).toMatch(/Foo!/) + expect(pText).toMatch('buffered metrics: 0') expect(firstContentfulPaint).not.toBeNaN() expect(firstContentfulPaint).toBeGreaterThan(0) diff --git a/test/integration/relay-analytics/pages/index.js b/test/integration/relay-analytics/pages/index.js index 8f3e7c75b3d63b8..c10829859fd1cc5 100644 --- a/test/integration/relay-analytics/pages/index.js +++ b/test/integration/relay-analytics/pages/index.js @@ -1,5 +1,6 @@ /* global localStorage */ import { unstable_useWebVitalsReport } from 'next/vitals' +import { getBufferedVitalsMetrics } from 'next/dist/client/vitals' if (typeof navigator !== 'undefined') { window.__BEACONS = window.__BEACONS || [] @@ -37,6 +38,7 @@ export default () => {

Foo!

bar!

+

{`buffered metrics: ${getBufferedVitalsMetrics().length}`}

) } diff --git a/test/integration/relay-analytics/test/index.test.js b/test/integration/relay-analytics/test/index.test.js index 88e3c7a0c7d4862..4a5f6537a967140 100644 --- a/test/integration/relay-analytics/test/index.test.js +++ b/test/integration/relay-analytics/test/index.test.js @@ -67,6 +67,7 @@ function runTest() { await browser.waitForElementByCss('h1') const h1Text = await browser.elementByCss('h1').text() + const pText = await browser.elementByCss('p').text() const data = parseFloat( await browser.eval('localStorage.getItem("Next.js-hydration")') ) @@ -81,6 +82,7 @@ function runTest() { ) let cls = await browser.eval('localStorage.getItem("CLS")') expect(h1Text).toMatch(/Foo!/) + expect(pText).toMatch('buffered metrics: 0') expect(data).not.toBeNaN() expect(data).toBeGreaterThan(0) expect(firstByte).not.toBeNaN()