From cbc2d08ba70cd1f6457150583dfb2bce7d01b65d Mon Sep 17 00:00:00 2001 From: Islam Shehata Date: Mon, 20 Mar 2023 15:00:21 +0200 Subject: [PATCH 1/7] add useReportWebVitals that makes use of web-vitals package --- packages/next/package.json | 3 ++- packages/next/src/client/index.tsx | 2 ++ packages/next/src/client/web-vitals.ts | 13 +++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 packages/next/src/client/web-vitals.ts diff --git a/packages/next/package.json b/packages/next/package.json index 6e878e42118a..06cc5c8cf058 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -84,7 +84,8 @@ "@swc/helpers": "0.4.14", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", - "styled-jsx": "5.1.1" + "styled-jsx": "5.1.1", + "web-vitals": "^3.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.4.1", diff --git a/packages/next/src/client/index.tsx b/packages/next/src/client/index.tsx index 554fdfe5d1e7..14cef8672485 100644 --- a/packages/next/src/client/index.tsx +++ b/packages/next/src/client/index.tsx @@ -911,3 +911,5 @@ export async function hydrate(opts?: { beforeRender?: () => Promise }) { render(renderCtx) } + +export { useReportWebVitals } from './web-vitals' diff --git a/packages/next/src/client/web-vitals.ts b/packages/next/src/client/web-vitals.ts new file mode 100644 index 000000000000..b80ab2b917c1 --- /dev/null +++ b/packages/next/src/client/web-vitals.ts @@ -0,0 +1,13 @@ +import { useEffect } from 'react' +import { onLCP, onFID, onCLS, onINP, onFCP, onTTFB, Metric } from 'web-vitals' + +export function useReportWebVitals(reportWebVitalsFn: (metric: Metric) => {}) { + useEffect(() => { + onCLS(reportWebVitalsFn) + onFID(reportWebVitalsFn) + onLCP(reportWebVitalsFn) + onINP(reportWebVitalsFn) + onFCP(reportWebVitalsFn) + onTTFB(reportWebVitalsFn) + }, [reportWebVitalsFn]) +} From 08cf530cdc50eba20233a7864cc3733d8d057ada Mon Sep 17 00:00:00 2001 From: Islam Shehata Date: Mon, 20 Mar 2023 15:45:35 +0200 Subject: [PATCH 2/7] correctly use web-vitals from compiled dist --- packages/next/package.json | 3 +-- packages/next/src/client/web-vitals.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/next/package.json b/packages/next/package.json index 06cc5c8cf058..6e878e42118a 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -84,8 +84,7 @@ "@swc/helpers": "0.4.14", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", - "styled-jsx": "5.1.1", - "web-vitals": "^3.1.1" + "styled-jsx": "5.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.4.1", diff --git a/packages/next/src/client/web-vitals.ts b/packages/next/src/client/web-vitals.ts index b80ab2b917c1..c25b67ce568e 100644 --- a/packages/next/src/client/web-vitals.ts +++ b/packages/next/src/client/web-vitals.ts @@ -1,5 +1,13 @@ import { useEffect } from 'react' -import { onLCP, onFID, onCLS, onINP, onFCP, onTTFB, Metric } from 'web-vitals' +import { + onLCP, + onFID, + onCLS, + onINP, + onFCP, + onTTFB, + Metric, +} from 'next/dist/compiled/web-vitals' export function useReportWebVitals(reportWebVitalsFn: (metric: Metric) => {}) { useEffect(() => { From f3d61d61dbd05d503649edca22e815cfc388bd59 Mon Sep 17 00:00:00 2001 From: Islam Shehata Date: Mon, 20 Mar 2023 17:31:37 +0200 Subject: [PATCH 3/7] prepare integration test for useReportWebVitals --- packages/next/src/client/web-vitals.ts | 4 +- .../reportWebVitals/components/reporter.js | 11 +++++ .../reportWebVitals/next.config.js | 1 + .../reportWebVitals/pages/index.js | 10 ++++ .../reportWebVitals/test/index.test.js | 47 +++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 test/integration/reportWebVitals/components/reporter.js create mode 100644 test/integration/reportWebVitals/next.config.js create mode 100644 test/integration/reportWebVitals/pages/index.js create mode 100644 test/integration/reportWebVitals/test/index.test.js diff --git a/packages/next/src/client/web-vitals.ts b/packages/next/src/client/web-vitals.ts index c25b67ce568e..4194cf90f566 100644 --- a/packages/next/src/client/web-vitals.ts +++ b/packages/next/src/client/web-vitals.ts @@ -9,7 +9,9 @@ import { Metric, } from 'next/dist/compiled/web-vitals' -export function useReportWebVitals(reportWebVitalsFn: (metric: Metric) => {}) { +export function useReportWebVitals( + reportWebVitalsFn: (metric: Metric) => void +) { useEffect(() => { onCLS(reportWebVitalsFn) onFID(reportWebVitalsFn) diff --git a/test/integration/reportWebVitals/components/reporter.js b/test/integration/reportWebVitals/components/reporter.js new file mode 100644 index 000000000000..b65c63e780c0 --- /dev/null +++ b/test/integration/reportWebVitals/components/reporter.js @@ -0,0 +1,11 @@ +'use client' +import 'client-only' +import { useReportWebVitals } from 'next/client' + +const report = (metric) => { + console.log(metric) +} + +export default function Reporter() { + useReportWebVitals(report) +} diff --git a/test/integration/reportWebVitals/next.config.js b/test/integration/reportWebVitals/next.config.js new file mode 100644 index 000000000000..4ba52ba2c8df --- /dev/null +++ b/test/integration/reportWebVitals/next.config.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/test/integration/reportWebVitals/pages/index.js b/test/integration/reportWebVitals/pages/index.js new file mode 100644 index 000000000000..156f820dc9dd --- /dev/null +++ b/test/integration/reportWebVitals/pages/index.js @@ -0,0 +1,10 @@ +import Reporter from '../components/reporter' + +export default function component() { + return ( + <> +

Test

+ + + ) +} diff --git a/test/integration/reportWebVitals/test/index.test.js b/test/integration/reportWebVitals/test/index.test.js new file mode 100644 index 000000000000..cf8d91780fa1 --- /dev/null +++ b/test/integration/reportWebVitals/test/index.test.js @@ -0,0 +1,47 @@ +import { join } from 'path' +import { + findPort, + launchApp, + killApp, + nextStart, + nextBuild, + waitFor, +} from 'next-test-utils' +import webdriver from 'next-webdriver' + +let app +let appPort +const appDir = join(__dirname, '..') + +const runTests = () => { + const consoleSpy = jest.spyOn(console, 'log') + + it('should print web-vitals to console', async () => { + await webdriver(appPort, '/') + await waitFor(1000) + expect(consoleSpy).toBeCalledWith({}) + }) +} + +describe('useReportWebVitals', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + }) + afterAll(() => killApp(app)) + + runTests() + }) + + describe('production mode', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(() => killApp(app)) + + runTests() + }) +}) From be2fbad345b6763997bfe1b1b5290ab6b24a8459 Mon Sep 17 00:00:00 2001 From: Islam Shehata Date: Mon, 20 Mar 2023 17:51:14 +0200 Subject: [PATCH 4/7] remove import client-only --- test/integration/reportWebVitals/components/reporter.js | 1 - test/integration/reportWebVitals/test/index.test.js | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/integration/reportWebVitals/components/reporter.js b/test/integration/reportWebVitals/components/reporter.js index b65c63e780c0..9186a8fe6e79 100644 --- a/test/integration/reportWebVitals/components/reporter.js +++ b/test/integration/reportWebVitals/components/reporter.js @@ -1,5 +1,4 @@ 'use client' -import 'client-only' import { useReportWebVitals } from 'next/client' const report = (metric) => { diff --git a/test/integration/reportWebVitals/test/index.test.js b/test/integration/reportWebVitals/test/index.test.js index cf8d91780fa1..fd5a1345f232 100644 --- a/test/integration/reportWebVitals/test/index.test.js +++ b/test/integration/reportWebVitals/test/index.test.js @@ -14,12 +14,11 @@ let appPort const appDir = join(__dirname, '..') const runTests = () => { - const consoleSpy = jest.spyOn(console, 'log') - it('should print web-vitals to console', async () => { + const consoleSpy = jest.spyOn(console, 'log') await webdriver(appPort, '/') await waitFor(1000) - expect(consoleSpy).toBeCalledWith({}) + expect(consoleSpy).toHaveBeenCalledWith({}) }) } From 04d922c44ca04dc58d8ed132c1587491654097ed Mon Sep 17 00:00:00 2001 From: Islam Shehata Date: Tue, 21 Mar 2023 13:52:13 +0200 Subject: [PATCH 5/7] write e2e test for useReportWebVitals --- .../app/app/report-web-vitals/layout.js | 19 +++++++ .../app/app/report-web-vitals/page.js} | 2 +- .../app/app/report-web-vitals/reporter.js | 17 ++++++ .../app-dir/app/useReportWebVitals.test.ts | 53 +++++++++++++++++++ .../reportWebVitals/components/reporter.js | 10 ---- .../reportWebVitals/next.config.js | 1 - .../reportWebVitals/test/index.test.js | 46 ---------------- 7 files changed, 90 insertions(+), 58 deletions(-) create mode 100644 test/e2e/app-dir/app/app/report-web-vitals/layout.js rename test/{integration/reportWebVitals/pages/index.js => e2e/app-dir/app/app/report-web-vitals/page.js} (70%) 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 delete mode 100644 test/integration/reportWebVitals/components/reporter.js delete mode 100644 test/integration/reportWebVitals/next.config.js delete mode 100644 test/integration/reportWebVitals/test/index.test.js 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/integration/reportWebVitals/pages/index.js b/test/e2e/app-dir/app/app/report-web-vitals/page.js similarity index 70% rename from test/integration/reportWebVitals/pages/index.js rename to test/e2e/app-dir/app/app/report-web-vitals/page.js index 156f820dc9dd..fc54c58899e2 100644 --- a/test/integration/reportWebVitals/pages/index.js +++ b/test/e2e/app-dir/app/app/report-web-vitals/page.js @@ -1,4 +1,4 @@ -import Reporter from '../components/reporter' +import Reporter from './reporter' export default function component() { return ( 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..655bbdd42eaf --- /dev/null +++ b/test/e2e/app-dir/app/app/report-web-vitals/reporter.js @@ -0,0 +1,17 @@ +'use client' +import { useReportWebVitals } from 'next/client' + +const report = (metric) => { + const blob = new Blob([new URLSearchParams(metric).toString()]) + const vitalsUrl = 'https://vitals.vercel-insights.com/v1/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..80c267f73a9c --- /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://vitals.vercel-insights.com/v1/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/) + }) +}) diff --git a/test/integration/reportWebVitals/components/reporter.js b/test/integration/reportWebVitals/components/reporter.js deleted file mode 100644 index 9186a8fe6e79..000000000000 --- a/test/integration/reportWebVitals/components/reporter.js +++ /dev/null @@ -1,10 +0,0 @@ -'use client' -import { useReportWebVitals } from 'next/client' - -const report = (metric) => { - console.log(metric) -} - -export default function Reporter() { - useReportWebVitals(report) -} diff --git a/test/integration/reportWebVitals/next.config.js b/test/integration/reportWebVitals/next.config.js deleted file mode 100644 index 4ba52ba2c8df..000000000000 --- a/test/integration/reportWebVitals/next.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/test/integration/reportWebVitals/test/index.test.js b/test/integration/reportWebVitals/test/index.test.js deleted file mode 100644 index fd5a1345f232..000000000000 --- a/test/integration/reportWebVitals/test/index.test.js +++ /dev/null @@ -1,46 +0,0 @@ -import { join } from 'path' -import { - findPort, - launchApp, - killApp, - nextStart, - nextBuild, - waitFor, -} from 'next-test-utils' -import webdriver from 'next-webdriver' - -let app -let appPort -const appDir = join(__dirname, '..') - -const runTests = () => { - it('should print web-vitals to console', async () => { - const consoleSpy = jest.spyOn(console, 'log') - await webdriver(appPort, '/') - await waitFor(1000) - expect(consoleSpy).toHaveBeenCalledWith({}) - }) -} - -describe('useReportWebVitals', () => { - describe('dev mode', () => { - beforeAll(async () => { - appPort = await findPort() - app = await launchApp(appDir, appPort) - }) - afterAll(() => killApp(app)) - - runTests() - }) - - describe('production mode', () => { - beforeAll(async () => { - await nextBuild(appDir) - appPort = await findPort() - app = await nextStart(appDir, appPort) - }) - afterAll(() => killApp(app)) - - runTests() - }) -}) From 9bb73594a57dfc3a6bea17a5b46c720ddb14c627 Mon Sep 17 00:00:00 2001 From: Islam Shehata Date: Thu, 23 Mar 2023 12:58:28 +0200 Subject: [PATCH 6/7] export reportWebVitals hook from client/web-vitals --- packages/next/package.json | 3 ++- packages/next/src/client/index.tsx | 2 -- packages/next/web-vitals.d.ts | 1 + packages/next/web-vitals.js | 1 + test/e2e/app-dir/app/app/report-web-vitals/reporter.js | 4 ++-- test/e2e/app-dir/app/useReportWebVitals.test.ts | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 packages/next/web-vitals.d.ts create mode 100644 packages/next/web-vitals.js diff --git a/packages/next/package.json b/packages/next/package.json index 77f50890cb1c..fc4640d1e83e 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -57,7 +57,8 @@ "navigation.d.ts", "headers.js", "headers.d.ts", - "navigation-types" + "navigation-types", + "web-vitals" ], "bin": { "next": "./dist/bin/next" diff --git a/packages/next/src/client/index.tsx b/packages/next/src/client/index.tsx index 14cef8672485..554fdfe5d1e7 100644 --- a/packages/next/src/client/index.tsx +++ b/packages/next/src/client/index.tsx @@ -911,5 +911,3 @@ export async function hydrate(opts?: { beforeRender?: () => Promise }) { render(renderCtx) } - -export { useReportWebVitals } from './web-vitals' 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/reporter.js b/test/e2e/app-dir/app/app/report-web-vitals/reporter.js index 655bbdd42eaf..c953e96a58e0 100644 --- a/test/e2e/app-dir/app/app/report-web-vitals/reporter.js +++ b/test/e2e/app-dir/app/app/report-web-vitals/reporter.js @@ -1,9 +1,9 @@ 'use client' -import { useReportWebVitals } from 'next/client' +import { useReportWebVitals } from 'next/web-vitals' const report = (metric) => { const blob = new Blob([new URLSearchParams(metric).toString()]) - const vitalsUrl = 'https://vitals.vercel-insights.com/v1/vitals' + const vitalsUrl = 'https://example.vercel.sh/vitals' fetch(vitalsUrl, { body: blob, method: 'POST', diff --git a/test/e2e/app-dir/app/useReportWebVitals.test.ts b/test/e2e/app-dir/app/useReportWebVitals.test.ts index 80c267f73a9c..59b98b607428 100644 --- a/test/e2e/app-dir/app/useReportWebVitals.test.ts +++ b/test/e2e/app-dir/app/useReportWebVitals.test.ts @@ -28,7 +28,7 @@ describe('useReportWebVitals hook', () => { let eventsCount = 0 const browser = await next.browser('/report-web-vitals', { beforePageLoad: (page) => { - page.route('https://vitals.vercel-insights.com/v1/vitals', (route) => { + page.route('https://example.vercel.sh/vitals', (route) => { if (countEvents) { eventsCount += 1 } From d6fadc785a7e16b56f999c0984685708a155589e Mon Sep 17 00:00:00 2001 From: Islam Shehata Date: Thu, 23 Mar 2023 17:53:20 +0200 Subject: [PATCH 7/7] fix web-vitals.js path in package.json files --- packages/next/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/next/package.json b/packages/next/package.json index 58f8e9d407ef..6fe1cb042e63 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -58,7 +58,8 @@ "headers.js", "headers.d.ts", "navigation-types", - "web-vitals" + "web-vitals.js", + "web-vitals.d.ts" ], "bin": { "next": "./dist/bin/next"