From b39a03f4e3fdb5811d71ed7b8623388e654d5351 Mon Sep 17 00:00:00 2001 From: Islam Shehata Date: Fri, 24 Mar 2023 12:13:50 +0200 Subject: [PATCH] add useReportWebVitals that makes use of web-vitals package (#47319) ### What? introduce a new hook `useReportWebVitals` that would register a function to handle web-vitals metrics. ### Why? next.js users who use [Axiom](https://axiom.co) has been [asking](https://github.com/axiomhq/next-axiom/issues/109) for this feature for nextjs 13, as it was working for nextjs 12. --- packages/next/package.json | 4 +- packages/next/src/client/web-vitals.ts | 23 ++++++++ packages/next/web-vitals.d.ts | 1 + packages/next/web-vitals.js | 1 + .../app/app/report-web-vitals/layout.js | 19 +++++++ .../app-dir/app/app/report-web-vitals/page.js | 10 ++++ .../app/app/report-web-vitals/reporter.js | 17 ++++++ .../app-dir/app/useReportWebVitals.test.ts | 53 +++++++++++++++++++ 8 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 packages/next/src/client/web-vitals.ts create mode 100644 packages/next/web-vitals.d.ts create mode 100644 packages/next/web-vitals.js create mode 100644 test/e2e/app-dir/app/app/report-web-vitals/layout.js create mode 100644 test/e2e/app-dir/app/app/report-web-vitals/page.js create mode 100644 test/e2e/app-dir/app/app/report-web-vitals/reporter.js create mode 100644 test/e2e/app-dir/app/useReportWebVitals.test.ts diff --git a/packages/next/package.json b/packages/next/package.json index ef03cab78be5..de650d6b5fb9 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -57,7 +57,9 @@ "navigation.d.ts", "headers.js", "headers.d.ts", - "navigation-types" + "navigation-types", + "web-vitals.js", + "web-vitals.d.ts" ], "bin": { "next": "./dist/bin/next" diff --git a/packages/next/src/client/web-vitals.ts b/packages/next/src/client/web-vitals.ts new file mode 100644 index 000000000000..4194cf90f566 --- /dev/null +++ b/packages/next/src/client/web-vitals.ts @@ -0,0 +1,23 @@ +import { useEffect } from 'react' +import { + onLCP, + onFID, + onCLS, + onINP, + onFCP, + onTTFB, + Metric, +} from 'next/dist/compiled/web-vitals' + +export function useReportWebVitals( + reportWebVitalsFn: (metric: Metric) => void +) { + useEffect(() => { + onCLS(reportWebVitalsFn) + onFID(reportWebVitalsFn) + onLCP(reportWebVitalsFn) + onINP(reportWebVitalsFn) + onFCP(reportWebVitalsFn) + onTTFB(reportWebVitalsFn) + }, [reportWebVitalsFn]) +} diff --git a/packages/next/web-vitals.d.ts b/packages/next/web-vitals.d.ts new file mode 100644 index 000000000000..83f283d4a17e --- /dev/null +++ b/packages/next/web-vitals.d.ts @@ -0,0 +1 @@ +export * from './dist/client/web-vitals' diff --git a/packages/next/web-vitals.js b/packages/next/web-vitals.js new file mode 100644 index 000000000000..f6123622a020 --- /dev/null +++ b/packages/next/web-vitals.js @@ -0,0 +1 @@ +module.exports = require('./dist/client/web-vitals') diff --git a/test/e2e/app-dir/app/app/report-web-vitals/layout.js b/test/e2e/app-dir/app/app/report-web-vitals/layout.js new file mode 100644 index 000000000000..3b12807859d0 --- /dev/null +++ b/test/e2e/app-dir/app/app/report-web-vitals/layout.js @@ -0,0 +1,19 @@ +'use client' + +import { useState, useEffect } from 'react' + +export default function ClientNestedLayout({ children }) { + const [count, setCount] = useState(0) + useEffect(() => { + setCount(1) + }, []) + return ( + <> +

Client Nested. Count: {count}

+ + {children} + + ) +} diff --git a/test/e2e/app-dir/app/app/report-web-vitals/page.js b/test/e2e/app-dir/app/app/report-web-vitals/page.js new file mode 100644 index 000000000000..fc54c58899e2 --- /dev/null +++ b/test/e2e/app-dir/app/app/report-web-vitals/page.js @@ -0,0 +1,10 @@ +import Reporter from './reporter' + +export default function component() { + return ( + <> +

Test

+ + + ) +} diff --git a/test/e2e/app-dir/app/app/report-web-vitals/reporter.js b/test/e2e/app-dir/app/app/report-web-vitals/reporter.js new file mode 100644 index 000000000000..c953e96a58e0 --- /dev/null +++ b/test/e2e/app-dir/app/app/report-web-vitals/reporter.js @@ -0,0 +1,17 @@ +'use client' +import { useReportWebVitals } from 'next/web-vitals' + +const report = (metric) => { + const blob = new Blob([new URLSearchParams(metric).toString()]) + const vitalsUrl = 'https://example.vercel.sh/vitals' + fetch(vitalsUrl, { + body: blob, + method: 'POST', + credentials: 'omit', + keepalive: true, + }) +} + +export default function Reporter() { + useReportWebVitals(report) +} diff --git a/test/e2e/app-dir/app/useReportWebVitals.test.ts b/test/e2e/app-dir/app/useReportWebVitals.test.ts new file mode 100644 index 000000000000..59b98b607428 --- /dev/null +++ b/test/e2e/app-dir/app/useReportWebVitals.test.ts @@ -0,0 +1,53 @@ +import { createNext } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { check } from 'next-test-utils' + +describe('useReportWebVitals hook', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: __dirname, + dependencies: { + swr: '2.0.0-rc.0', + react: 'latest', + 'react-dom': 'latest', + sass: 'latest', + }, + skipStart: true, + env: {}, + }) + + await next.start() + }) + afterAll(() => next.destroy()) + + // Analytics events are only sent in production + it('should send web-vitals to vercel-insights', async () => { + let countEvents = false + let eventsCount = 0 + const browser = await next.browser('/report-web-vitals', { + beforePageLoad: (page) => { + page.route('https://example.vercel.sh/vitals', (route) => { + if (countEvents) { + eventsCount += 1 + } + + route.fulfill() + }) + }, + }) + + // Start counting analytics events + countEvents = true + + // Refresh will trigger CLS and LCP. When page loads FCP and TTFB will trigger: + await browser.refresh() + + // After interaction LCP and FID will trigger + await browser.elementById('btn').click() + + // Make sure all registered events in performance-relayer has fired + await check(() => eventsCount, /6/) + }) +})