diff --git a/docs/01-app/02-guides/analytics.mdx b/docs/01-app/02-guides/analytics.mdx index 72717de9365b..9e14715567df 100644 --- a/docs/01-app/02-guides/analytics.mdx +++ b/docs/01-app/02-guides/analytics.mdx @@ -88,7 +88,6 @@ experience of a web page. The following web vitals are all included: - [Time to First Byte](https://developer.mozilla.org/docs/Glossary/Time_to_first_byte) (TTFB) - [First Contentful Paint](https://developer.mozilla.org/docs/Glossary/First_contentful_paint) (FCP) - [Largest Contentful Paint](https://web.dev/lcp/) (LCP) -- [First Input Delay](https://web.dev/fid/) (FID) - [Cumulative Layout Shift](https://web.dev/cls/) (CLS) - [Interaction to Next Paint](https://web.dev/inp/) (INP) diff --git a/docs/01-app/02-guides/migrating-to-cache-components.mdx b/docs/01-app/02-guides/migrating-to-cache-components.mdx index a6bdd848f4a5..ab0a20d8f4cb 100644 --- a/docs/01-app/02-guides/migrating-to-cache-components.mdx +++ b/docs/01-app/02-guides/migrating-to-cache-components.mdx @@ -89,7 +89,7 @@ export const instant = false You don't have to migrate every route at once. `instant = false` lets you get the whole app building and running first, then convert routes one at a time: -1. **Enable the flag and remove the route segment configs** (`dynamic`, `revalidate`, `fetchCache`). Routes that still render instantly need no further work. +1. **Enable the flag and migrate each route segment config.** Follow the relevant sections below for [`dynamic = "force-dynamic"`](#dynamic--force-dynamic), [`dynamic = "force-static"`](#dynamic--force-static), [`revalidate`](#revalidate), and [`fetchCache`](#fetchcache). For routes with dynamic params, also follow the [`generateStaticParams`](#generatestaticparams-and-dynamicparams) guidance. Routes that still render instantly need no further work. 2. **Opt out the routes that aren't ready.** Where an insight or error appears, set `instant = false` on the segment that raised it. To do this in one pass across the whole app, run the [`cache-components-instant-false`](/docs/app/guides/upgrading/codemods#cache-components-instant-false) codemod, which adds the opt-out to every `page`, `layout`, and `default` that doesn't already declare `instant`: ```bash filename="Terminal" @@ -569,6 +569,8 @@ Cache Components changes how [dynamic routes](/docs/app/api-reference/file-conve **Returning an empty array now errors.** Without Cache Components, returning `[]` defers every path to the first runtime visit. With Cache Components, [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) must return at least one param so Next.js can prerender the route and validate it produces a non-empty [static shell](/docs/app/glossary#static-shell). An empty array raises [`empty-generate-static-params`](/docs/messages/empty-generate-static-params). +Keep `generateStaticParams` and return at least one real param. Removing the export opts the route out of ISR, so Next.js renders it on every request, even when it uses `use cache` for data. Read [ISR with Cache Components](/docs/app/guides/incremental-static-regeneration-cache-components) to learn how `generateStaticParams` prerenders dynamic routes and upgrades unlisted paths after their first visit. + ```tsx filename="app/blog/[slug]/page.tsx" switcher // Before - defer all paths to runtime export async function generateStaticParams() { diff --git a/docs/01-app/03-api-reference/04-functions/use-report-web-vitals.mdx b/docs/01-app/03-api-reference/04-functions/use-report-web-vitals.mdx index 079b83dc9735..6182e48c50fd 100644 --- a/docs/01-app/03-api-reference/04-functions/use-report-web-vitals.mdx +++ b/docs/01-app/03-api-reference/04-functions/use-report-web-vitals.mdx @@ -69,10 +69,14 @@ export default function Layout({ children }) { The `metric` object passed as the hook's argument consists of a number of properties: - `id`: Unique identifier for the metric in the context of the current page load -- `name`: The name of the performance metric. Possible values include names of [Web Vitals](#web-vitals) metrics (TTFB, FCP, LCP, FID, CLS) specific to a web application. +- `name`: The name of the performance metric. Possible values include names of [Web Vitals](#web-vitals) metrics (TTFB, FCP, LCP, INP, CLS) specific to a web application. - `delta`: The difference between the current value and the previous value of the metric. The value is typically in milliseconds and represents the change in the metric's value over time. - `entries`: An array of [Performance Entries](https://developer.mozilla.org/docs/Web/API/PerformanceEntry) associated with the metric. These entries provide detailed information about the performance events related to the metric. -- `navigationType`: Indicates the navigation type that triggered metric collection. Values are derived from [PerformanceNavigationTiming.type](https://developer.mozilla.org/docs/Web/API/PerformanceNavigationTiming/type) and may include `"navigate"`, `"reload"`, `"prerender"`, `"back-forward"` (normalized from `"back_forward"`), `"back-forward-cache"` (BFCache restore), and `"restore"` (page restored after discard). +- `navigationType`: Indicates the navigation type that triggered metric collection. Values are derived from [PerformanceNavigationTiming.type](https://developer.mozilla.org/docs/Web/API/PerformanceNavigationTiming/type) and may include `"navigate"`, `"reload"`, `"prerender"`, `"back-forward"` (normalized from `"back_forward"`), `"back-forward-cache"` (BFCache restore), `"restore"` (page restored after discard), and `"soft-navigation"`. +- `navigationId`: The ID of the navigation for which the metric was measured. +- `navigationURL`: The URL of the navigation for which the metric was measured. This is especially useful for soft navigations because a metric may be reported after the URL has changed again. +- `navigationStartTime`: The start time of the navigation for which the metric was measured. +- `navigationInteractionId`: For soft navigations, the interaction ID that triggered the navigation. - `rating`: A qualitative rating of the metric value, providing an assessment of the performance. Possible values are `"good"`, `"needs-improvement"`, and `"poor"`. The rating is typically determined by comparing the metric value against predefined thresholds that indicate acceptable or suboptimal performance. - `value`: The actual value or duration of the performance entry, typically in milliseconds. The value provides a quantitative measure of the performance aspect being tracked by the metric. The source of the value depends on the specific metric being measured and can come from various [Performance API](https://developer.mozilla.org/docs/Web/API/Performance_API)s. @@ -84,7 +88,6 @@ experience of a web page. The following web vitals are all included: - [Time to First Byte](https://developer.mozilla.org/docs/Glossary/Time_to_first_byte) (TTFB) - [First Contentful Paint](https://developer.mozilla.org/docs/Glossary/First_contentful_paint) (FCP) - [Largest Contentful Paint](https://web.dev/lcp/) (LCP) -- [First Input Delay](https://web.dev/fid/) (FID) - [Cumulative Layout Shift](https://web.dev/cls/) (CLS) - [Interaction to Next Paint](https://web.dev/inp/) (INP) diff --git a/docs/01-app/03-api-reference/05-config/01-next-config-js/taint.mdx b/docs/01-app/03-api-reference/05-config/01-next-config-js/taint.mdx index 130e62dd728b..e2363b118272 100644 --- a/docs/01-app/03-api-reference/05-config/01-next-config-js/taint.mdx +++ b/docs/01-app/03-api-reference/05-config/01-next-config-js/taint.mdx @@ -11,7 +11,7 @@ The `taint` option enables support for experimental React APIs for tainting obje - [`experimental_taintObjectReference`](https://react.dev/reference/react/experimental_taintObjectReference) taint objects references. - [`experimental_taintUniqueValue`](https://react.dev/reference/react/experimental_taintUniqueValue) to taint unique values. -> **Good to know**: Activating this flag also enables the React `experimental` channel for `app` directory. +> **Good to know**: Activating this flag also enables the React `experimental` channel for `app` directory, and taints `process.env` so it cannot be passed whole to a Client Component. ```ts filename="next.config.ts" switcher import type { NextConfig } from 'next' @@ -51,6 +51,7 @@ It is recommended to model your data and APIs so that sensitive data is not retu ## Caveats - Tainting can only keep track of objects by reference. Copying an object creates an untainted version, which loses all guarantees given by the API. You'll need to taint the copy. +- The built-in `process.env` taint applies to the object reference only. Reading individual variables such as `process.env.MY_VAR` and passing the resulting string to a Client Component is unaffected, as is passing a copy like `{ ...process.env }`. - Tainting cannot keep track of data derived from a tainted value. You also need to taint the derived value. - Values are tainted for as long as their lifetime reference is within scope. See the [`experimental_taintUniqueValue` parameters reference](https://react.dev/reference/react/experimental_taintUniqueValue#parameters), for more information. diff --git a/evals/eval.config.json b/evals/eval.config.json index 45b3b89547d7..270e80dabb9c 100644 --- a/evals/eval.config.json +++ b/evals/eval.config.json @@ -6,5 +6,9 @@ "agent-047-adopt-cache-components": { "skills": ["next-cache-components-adoption"], "timeout": 1800 + }, + "agent-054-cache-components-empty-static-params": { + "skills": ["next-cache-components-adoption"], + "timeout": 1800 } } diff --git a/evals/evals/agent-054-cache-components-empty-static-params/EVAL.ts b/evals/evals/agent-054-cache-components-empty-static-params/EVAL.ts new file mode 100644 index 000000000000..404b4239ac12 --- /dev/null +++ b/evals/evals/agent-054-cache-components-empty-static-params/EVAL.ts @@ -0,0 +1,38 @@ +/** + * Preserve on-demand ISR when adopting Cache Components + * + * The starting route uses the previous on-demand ISR pattern: + * `force-static`, `revalidate`, and an empty `generateStaticParams` result. + * Cache Components rejects an empty result, but deleting the function changes + * the route to request-time rendering. The migration must retain the export + * and give it at least one param so other params can still be cached on demand. + */ + +import { expect, test } from 'vitest' +import { readFileSync } from 'fs' +import { join } from 'path' + +const config = readFileSync(join(process.cwd(), 'next.config.ts'), 'utf-8') +const eventPage = readFileSync( + join(process.cwd(), 'app/events/[slug]/page.tsx'), + 'utf-8' +) +const eventPageWithoutComments = eventPage + .replace(/\/\*[\s\S]*?\*\//g, '') + .replace(/\/\/.*$/gm, '') + +test('enables Cache Components', () => { + expect(config).toMatch(/cacheComponents\s*:\s*true/) +}) + +test('preserves generateStaticParams for on-demand ISR', () => { + expect(eventPageWithoutComments).toMatch( + /export\s+(?:(?:async\s+)?function\s+generateStaticParams\b|const\s+generateStaticParams\s*=)/ + ) +}) + +test('generateStaticParams no longer returns an empty array', () => { + expect(eventPageWithoutComments).not.toMatch( + /generateStaticParams[\s\S]*?return\s*\[\s*\]/ + ) +}) diff --git a/evals/evals/agent-054-cache-components-empty-static-params/PROMPT.md b/evals/evals/agent-054-cache-components-empty-static-params/PROMPT.md new file mode 100644 index 000000000000..b9545cfc2938 --- /dev/null +++ b/evals/evals/agent-054-cache-components-empty-static-params/PROMPT.md @@ -0,0 +1 @@ +Migrate this app to Cache Components incrementally. Make the smallest safe first change that enables Cache Components, keeps the build passing, and preserves the current rendering and caching behavior. diff --git a/evals/evals/agent-054-cache-components-empty-static-params/app/events/[slug]/page.tsx b/evals/evals/agent-054-cache-components-empty-static-params/app/events/[slug]/page.tsx new file mode 100644 index 000000000000..08a393aee24d --- /dev/null +++ b/evals/evals/agent-054-cache-components-empty-static-params/app/events/[slug]/page.tsx @@ -0,0 +1,27 @@ +import { notFound } from 'next/navigation' +import { getEvent } from '../../../lib/events' + +export const dynamic = 'force-static' +export const revalidate = 60 + +export function generateStaticParams() { + return [] +} + +export default async function EventPage({ + params, +}: { + params: Promise<{ slug: string }> +}) { + const { slug } = await params + const event = await getEvent(slug) + + if (!event) notFound() + + return ( +
+

{event.title}

+

{event.description}

+
+ ) +} diff --git a/evals/evals/agent-054-cache-components-empty-static-params/app/layout.tsx b/evals/evals/agent-054-cache-components-empty-static-params/app/layout.tsx new file mode 100644 index 000000000000..07e2db241f59 --- /dev/null +++ b/evals/evals/agent-054-cache-components-empty-static-params/app/layout.tsx @@ -0,0 +1,9 @@ +import type { ReactNode } from 'react' + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + {children} + + ) +} diff --git a/evals/evals/agent-054-cache-components-empty-static-params/app/page.tsx b/evals/evals/agent-054-cache-components-empty-static-params/app/page.tsx new file mode 100644 index 000000000000..1925e179ec1c --- /dev/null +++ b/evals/evals/agent-054-cache-components-empty-static-params/app/page.tsx @@ -0,0 +1,10 @@ +import Link from 'next/link' + +export default function HomePage() { + return ( +
+

Events

+ View the featured event +
+ ) +} diff --git a/evals/evals/agent-054-cache-components-empty-static-params/lib/events.ts b/evals/evals/agent-054-cache-components-empty-static-params/lib/events.ts new file mode 100644 index 000000000000..5fa8e58eed64 --- /dev/null +++ b/evals/evals/agent-054-cache-components-empty-static-params/lib/events.ts @@ -0,0 +1,12 @@ +export const FEATURED_EVENT_SLUG = 'launch-day' + +const events = { + [FEATURED_EVENT_SLUG]: { + title: 'Launch Day', + description: 'Follow the launch as it happens.', + }, +} as const + +export async function getEvent(slug: string) { + return events[slug as keyof typeof events] ?? null +} diff --git a/evals/evals/agent-054-cache-components-empty-static-params/next.config.ts b/evals/evals/agent-054-cache-components-empty-static-params/next.config.ts new file mode 100644 index 000000000000..e4f5738a310b --- /dev/null +++ b/evals/evals/agent-054-cache-components-empty-static-params/next.config.ts @@ -0,0 +1,5 @@ +import type { NextConfig } from 'next' + +const nextConfig: NextConfig = {} + +export default nextConfig diff --git a/evals/evals/agent-054-cache-components-empty-static-params/package.json b/evals/evals/agent-054-cache-components-empty-static-params/package.json new file mode 100644 index 000000000000..97f5fdec21f9 --- /dev/null +++ b/evals/evals/agent-054-cache-components-empty-static-params/package.json @@ -0,0 +1,23 @@ +{ + "private": true, + "type": "module", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "^16", + "react": "19.1.0", + "react-dom": "19.1.0" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "@vitejs/plugin-react": "^4.4.1", + "typescript": "^5", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.1.3" + } +} diff --git a/evals/evals/agent-054-cache-components-empty-static-params/tsconfig.json b/evals/evals/agent-054-cache-components-empty-static-params/tsconfig.json new file mode 100644 index 000000000000..64c21044c49f --- /dev/null +++ b/evals/evals/agent-054-cache-components-empty-static-params/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + }, + "target": "ES2017" + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/package.json b/package.json index e7a6efbc0294..14410601ce5c 100644 --- a/package.json +++ b/package.json @@ -338,7 +338,6 @@ "stacktrace-parser@0.1.10": "patches/stacktrace-parser@0.1.10.patch", "taskr@1.1.0": "patches/taskr@1.1.0.patch", "minizlib@3.1.0": "patches/minizlib@3.1.0.patch", - "web-vitals@4.2.1": "patches/web-vitals@4.2.1.patch", "@rspack/core@1.6.7": "patches/@rspack__core@1.6.7.patch", "@modelcontextprotocol/sdk": "patches/@modelcontextprotocol__sdk.patch", "@vercel/blob": "patches/@vercel__blob.patch", diff --git a/packages/next/package.json b/packages/next/package.json index 2444232a98cd..735cbee4cabf 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -349,7 +349,7 @@ "util": "0.12.4", "vm-browserify": "1.1.2", "watchpack": "2.4.0", - "web-vitals": "4.2.1", + "web-vitals": "6.2.1", "webpack": "5.98.0", "webpack-sources1": "npm:webpack-sources@1.4.3", "webpack-sources3": "npm:webpack-sources@3.2.3", diff --git a/packages/next/src/client/web-vitals.ts b/packages/next/src/client/web-vitals.ts index 36c532553bcf..e3ac3e76cb2d 100644 --- a/packages/next/src/client/web-vitals.ts +++ b/packages/next/src/client/web-vitals.ts @@ -1,7 +1,6 @@ import { useEffect } from 'react' import { onLCP, - onFID, onCLS, onINP, onFCP, @@ -13,11 +12,10 @@ export function useReportWebVitals( reportWebVitalsFn: (metric: Metric) => void ) { useEffect(() => { - onCLS(reportWebVitalsFn) - onFID(reportWebVitalsFn) - onLCP(reportWebVitalsFn) - onINP(reportWebVitalsFn) - onFCP(reportWebVitalsFn) + onCLS(reportWebVitalsFn, { reportSoftNavs: true }) + onLCP(reportWebVitalsFn, { reportSoftNavs: true }) + onINP(reportWebVitalsFn, { reportSoftNavs: true }) + onFCP(reportWebVitalsFn, { reportSoftNavs: true }) onTTFB(reportWebVitalsFn) }, [reportWebVitalsFn]) } diff --git a/packages/next/src/compiled/web-vitals-attribution/web-vitals.attribution.js b/packages/next/src/compiled/web-vitals-attribution/web-vitals.attribution.js index cf0431162458..2a7bdd8d78c2 100644 --- a/packages/next/src/compiled/web-vitals-attribution/web-vitals.attribution.js +++ b/packages/next/src/compiled/web-vitals-attribution/web-vitals.attribution.js @@ -1 +1 @@ -(function(){"use strict";var e={};!function(){e.d=function(c,h){for(var C in h){if(e.o(h,C)&&!e.o(c,C)){Object.defineProperty(c,C,{enumerable:true,get:h[C]})}}}}();!function(){e.o=function(e,c){return Object.prototype.hasOwnProperty.call(e,c)}}();!function(){e.r=function(e){if(typeof Symbol!=="undefined"&&Symbol.toStringTag){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})}Object.defineProperty(e,"__esModule",{value:true})}}();if(typeof e!=="undefined")e.ab=__dirname+"/";var c={};e.r(c);e.d(c,{CLSThresholds:function(){return O},FCPThresholds:function(){return A},FIDThresholds:function(){return bt},INPThresholds:function(){return G},LCPThresholds:function(){return ft},TTFBThresholds:function(){return gt},onCLS:function(){return w},onFCP:function(){return x},onFID:function(){return Tt},onINP:function(){return nt},onLCP:function(){return at},onTTFB:function(){return st}});var h,C,D,r=function(){var e=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart(c||100)-1)return h||D;if(h=h?D+">"+h:D,C.id)break;e=C.parentNode}}catch(e){}return h},I=-1,u=function(){return I},s=function(e){addEventListener("pageshow",(function(c){c.persisted&&(I=c.timeStamp,e(c))}),!0)},f=function(){var e=r();return e&&e.activationStart||0},d=function(e,c){var h=r(),C="navigate";u()>=0?C="back-forward-cache":h&&(document.prerendering||f()>0?C="prerender":document.wasDiscarded?C="restore":h.type&&(C=h.type.replace(/_/g,"-")));return{name:e,value:void 0===c?-1:c,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:C}},l=function(e,c,h){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){var C=new PerformanceObserver((function(e){Promise.resolve().then((function(){c(e.getEntries())}))}));return C.observe(Object.assign({type:e,buffered:!0},h||{})),C}}catch(e){}},m=function(e,c,h,C){var D,I;return function(k){c.value>=0&&(k||C)&&((I=c.value-(D||0))||void 0===D)&&(D=c.value,c.delta=I,c.rating=function(e,c){return e>c[1]?"poor":e>c[0]?"needs-improvement":"good"}(c.value,h),e(c))}},p=function(e){requestAnimationFrame((function(){return requestAnimationFrame((function(){return e()}))}))},v=function(e){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&e()}))},g=function(e){var c=!1;return function(){c||(e(),c=!0)}},k=-1,T=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},y=function(e){"hidden"===document.visibilityState&&k>-1&&(k="visibilitychange"===e.type?e.timeStamp:0,S())},E=function(){addEventListener("visibilitychange",y,!0),addEventListener("prerenderingchange",y,!0)},S=function(){removeEventListener("visibilitychange",y,!0),removeEventListener("prerenderingchange",y,!0)},b=function(){return k<0&&(k=T(),E(),s((function(){setTimeout((function(){k=T(),E()}),0)}))),{get firstHiddenTime(){return k}}},L=function(e){document.prerendering?addEventListener("prerenderingchange",(function(){return e()}),!0):e()},A=[1800,3e3],M=function(e,c){c=c||{},L((function(){var h,C=b(),D=d("FCP"),I=l("paint",(function(e){e.forEach((function(e){"first-contentful-paint"===e.name&&(I.disconnect(),e.startTimeC.value&&(C.value=D,C.entries=I,h())},k=l("layout-shift",o);k&&(h=m(e,C,O,c.reportAllChanges),v((function(){o(k.takeRecords()),h(!0)})),s((function(){D=0,C=d("CLS",0),h=m(e,C,O,c.reportAllChanges),p((function(){return h()}))})),setTimeout(h,0))})))}((function(c){var h=function(e){var c,h={};if(e.entries.length){var C=e.entries.reduce((function(e,c){return e&&e.value>c.value?e:c}));if(C&&C.sources&&C.sources.length){var D=(c=C.sources).find((function(e){return e.node&&1===e.node.nodeType}))||c[0];D&&(h={largestShiftTarget:o(D.node),largestShiftTime:C.startTime,largestShiftValue:C.value,largestShiftSource:D,largestShiftEntry:C,loadState:i(C.startTime)})}}return Object.assign(e,{attribution:h})}(c);e(h)}),c)},x=function(e,c){M((function(c){var h=function(e){var c={timeToFirstByte:0,firstByteToFCP:e.value,loadState:i(u())};if(e.entries.length){var h=r(),C=e.entries[e.entries.length-1];if(h){var D=h.activationStart||0,I=Math.max(0,h.responseStart-D);c={timeToFirstByte:I,firstByteToFCP:e.value-I,loadState:i(e.entries[0].startTime),navigationEntry:h,fcpEntry:C}}}return Object.assign(e,{attribution:c})}(c);e(h)}),c)},B=0,R=1/0,q=0,F=function(e){e.forEach((function(e){e.interactionId&&(R=Math.min(R,e.interactionId),q=Math.max(q,e.interactionId),B=q?(q-R)/7+1:0)}))},P=function(){"interactionCount"in performance||h||(h=l("event",F,{type:"event",buffered:!0,durationThreshold:0}))},_=[],W=new Map,U=0,j=function(){return(h?B:performance.interactionCount||0)-U},V=[],H=function(e){if(V.forEach((function(c){return c(e)})),e.interactionId||"first-input"===e.entryType){var c=_[_.length-1],h=W.get(e.interactionId);if(h||_.length<10||e.duration>c.latency){if(h)e.duration>h.latency?(h.entries=[e],h.latency=e.duration):e.duration===h.latency&&e.startTime===h.entries[0].startTime&&h.entries.push(e);else{var C={id:e.interactionId,latency:e.duration,entries:[e]};W.set(C.id,C),_.push(C)}_.sort((function(e,c){return c.latency-e.latency})),_.length>10&&_.splice(10).forEach((function(e){return W.delete(e.id)}))}}},N=function(e){var c=self.requestIdleCallback||self.setTimeout,h=-1;return e=g(e),"hidden"===document.visibilityState?e():(h=c(e),v(e)),h},G=[200,500],z=function(e,c){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(c=c||{},L((function(){var h;P();var C,D=d("INP"),a=function(e){N((function(){e.forEach(H);var c,h=(c=Math.min(_.length-1,Math.floor(j()/50)),_[c]);h&&h.latency!==D.value&&(D.value=h.latency,D.entries=h.entries,C())}))},I=l("event",a,{durationThreshold:null!==(h=c.durationThreshold)&&void 0!==h?h:40});C=m(e,D,G,c.reportAllChanges),I&&(I.observe({type:"first-input",buffered:!0}),v((function(){a(I.takeRecords()),C(!0)})),s((function(){U=0,_.length=0,W.clear(),D=d("INP"),C=m(e,D,G,c.reportAllChanges)})))})))},J=[],Y=[],Z=new WeakMap,$=new Map,tt=-1,K=function(e){J=J.concat(e),Q()},Q=function(){tt<0&&(tt=N(X))},X=function(){$.size>10&&$.forEach((function(e,c){W.has(c)||$.delete(c)}));var e=_.map((function(e){return Z.get(e.entries[0])})),c=Y.length-50;Y=Y.filter((function(h,C){return C>=c||e.includes(h)}));for(var h=new Set,C=0;C=0;C--){var I=Y[C];if(Math.abs(h-I.renderTime)<=8){(c=I).startTime=Math.min(e.startTime,c.startTime),c.processingStart=Math.min(e.processingStart,c.processingStart),c.processingEnd=Math.max(e.processingEnd,c.processingEnd),c.entries.push(e);break}}c||(c={startTime:e.startTime,processingStart:e.processingStart,processingEnd:e.processingEnd,renderTime:h,entries:[e]},Y.push(c)),(e.interactionId||"first-input"===e.entryType)&&Z.set(e,c),Q()}));var rt,it,ot,ct,et=function(e,c){for(var h,C=[],D=0;h=J[D];D++)if(!(h.startTime+h.durationc)break;C.push(h)}return C},nt=function(e,c){C||(C=l("long-animation-frame",K)),z((function(c){var h=function(e){var c=e.entries[0],h=Z.get(c),C=c.processingStart,D=h.processingEnd,I=h.entries.sort((function(e,c){return e.processingStart-c.processingStart})),k=et(c.startTime,D),A=e.entries.find((function(e){return e.target})),O=A&&A.target||$.get(c.interactionId),B=[c.startTime+c.duration,D].concat(k.map((function(e){return e.startTime+e.duration}))),R=Math.max.apply(Math,B),q={interactionTarget:o(O),interactionTargetElement:O,interactionType:c.name.startsWith("key")?"keyboard":"pointer",interactionTime:c.startTime,nextPaintTime:R,processedEventEntries:I,longAnimationFrameEntries:k,inputDelay:C-c.startTime,processingDuration:D-C,presentationDelay:Math.max(R-D,0),loadState:i(c.startTime)};return Object.assign(e,{attribution:q})}(c);e(h)}),c)},ft=[2500,4e3],dt={},at=function(e,c){!function(e,c){c=c||{},L((function(){var h,C=b(),D=d("LCP"),a=function(e){c.reportAllChanges||(e=e.slice(-1)),e.forEach((function(e){e.startTime=0&&it1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,c){var n=function(){lt(e,c),i()},r=function(){i()},i=function(){removeEventListener("pointerup",n,St),removeEventListener("pointercancel",r,St)};addEventListener("pointerup",n,St),addEventListener("pointercancel",r,St)}(c,e):lt(c,e)}},vt=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(c){return e(c,pt,St)}))},bt=[100,300],ht=function(e,c){c=c||{},L((function(){var h,C=b(),D=d("FID"),a=function(e){e.startTime{const a=performance.getEntriesByType("navigation")[0];if(a&&a.responseStart>0&&a.responseStart{if("loading"===document.readyState)return"loading";const S=n();if(S){if(a{const S=a.nodeName;return 1===a.nodeType?S.toLowerCase():S.toUpperCase().replace(/^#/,"")},i=a=>{let S="";try{for(;9!==a?.nodeType;){const I=a,C=I.id?"#"+I.id:[o(I),...Array.from(I.classList??[]).sort()].join(".");if(S.length+C.length>99)return S||C;if(S=S?C+">"+S:C,I.id)break;a=I.parentNode}}catch{}return S},I=new WeakMap;function r(a,S){let C=I.get(S);return C||(C=new WeakMap,I.set(S,C)),C.get(a)||C.set(a,new S),C.get(a)}let C=-1;const c=()=>C,f=a=>{addEventListener("pageshow",(S=>{S.persisted&&(C=S.timeStamp,a(S))}),!0)},l=(a,S,I,C)=>{let L,x;return B=>{S.value>=0&&(B||C)&&(x=S.value-(L??0),(x||void 0===L)&&(L=S.value,S.delta=x,S.rating=((a,S)=>a>S[1]?"poor":a>S[0]?"needs-improvement":"good")(S.value,I),a(S)))}},u=a=>{requestAnimationFrame((()=>requestAnimationFrame((()=>a()))))},d=()=>n()?.activationStart??0;let L=-1;const x=new Set,v=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,p=a=>{if("hidden"===document.visibilityState){if("visibilitychange"===a.type)for(const a of x)a();isFinite(L)||(L="visibilitychange"===a.type?a.timeStamp:0,removeEventListener("prerenderingchange",p,!0))}},m=(a=!1)=>{if(a&&(L=1/0),L<0){const a=d(),S=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find((S=>"hidden"===S.name&&S.startTime>=a))?.startTime;L=S??v(),addEventListener("visibilitychange",p,!0),addEventListener("prerenderingchange",p,!0),f((()=>{setTimeout((()=>{L=v()}))}))}return{get firstHiddenTime(){return L},onHidden(a){x.add(a)}}},y=(a,S=-1,I,C=0,L,x,B)=>{const j=n(),H=j?.navigationId||0;let $="navigate";I?$=I:c()>=0?$="back-forward-cache":j&&(document.prerendering||d()>0?$="prerender":document.wasDiscarded?$="restore":j.type&&($=j.type.replace(/_/g,"-")));return{name:a,value:S,rating:"good",delta:0,entries:[],id:`v6-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:$,navigationId:C||H,navigationInteractionId:L,navigationURL:x||j?.name,navigationStartTime:B||0}},b=(a,S,I={})=>{try{const C=a.filter((a=>PerformanceObserver.supportedEntryTypes.includes(a)));if(C.length>0){const a=new PerformanceObserver((a=>{queueMicrotask((()=>{const I=a.getEntries();C.length>1&&I.sort(((a,S)=>a.startTime+a.duration-(S.startTime+S.duration))),S(I)}))}));for(const S of C)a.observe({type:S,buffered:!0,...I});return a}}catch{}},M=a=>globalThis.PerformanceObserver?.supportedEntryTypes?.includes("soft-navigation")&&"function"==typeof globalThis.PerformanceSoftNavigation?.prototype?.getLargestInteractionContentfulPaint&&a&&a.reportSoftNavs,T=(a,S)=>{if(a.set(S.navigationId,S),a.size>2){const S=a.keys().next().value;void 0!==S&&a.delete(S)}},E=a=>{let S=!1;return()=>{S||(a(),S=!0)}};class D{u}const w=a=>{document.prerendering?addEventListener("prerenderingchange",a,!0):a()},B=[1800,3e3],k=(a,S={})=>{const I=M(S);w((()=>{const C=r(S,D),L=m();let x,j=y("FCP");const H=b(["paint"],(a=>{for(const S of a)"first-contentful-paint"===S.name&&(H.disconnect(),S.startTime{j=y("FCP",-1,"back-forward-cache",j.navigationId,j.navigationInteractionId,j.navigationURL,c()),x=l(a,j,B,S.reportAllChanges),u((()=>{j.value=performance.now()-I.timeStamp,x(!0)}))}))),I){b(["soft-navigation"],(I=>{I.forEach((I=>{C.u&&I.navigationId&&T(C.u,I);const L=Math.max((I.presentationTime||I.paintTime||0)-I.startTime,0);j=y("FCP",L,"soft-navigation",I.navigationId,I.interactionId,I.name,I.startTime),x=l(a,j,B,S.reportAllChanges),x(!0)}))}),S)}}))},j=[.1,.25],P=a=>a.find((a=>1===a.node?.nodeType))||a[0],F=(a,S={})=>{const I=r(S=Object.assign({},S),t),C=new WeakMap;I.t=a=>{if(a?.sources?.length){const I=P(a.sources),L=I?.node;if(L){const a=S.generateTarget?.(L)??i(L);C.set(I,a)}}};((a,S={})=>{const I=m();k(E((()=>{let C,L=y("CLS",0);const x=r(S,t),d=(I,B,H,$,V)=>{L=y("CLS",0,I,B,H,$,V),x.o=0,C=l(a,L,j,S.reportAllChanges)},h=(a=!1)=>{x.o>L.value&&(L.value=x.o,L.entries=x.i),C(a)},g=a=>{h(!0),d("soft-navigation",a.navigationId,a.interactionId,a.name,a.startTime)},v=a=>{for(const S of a)"soft-navigation"!==S.entryType?x.l(S):g(S);h()},B=["layout-shift"];M(S)&&B.push("soft-navigation");const H=b(B,v);H&&(C=l(a,L,j,S.reportAllChanges),I.onHidden((()=>{v(H.takeRecords()),C(!0)})),f((()=>{d("back-forward-cache",L.navigationId,L.navigationInteractionId,L.navigationURL,c()),u(C)})),setTimeout(C))})))})((S=>{a((a=>{let S={};if(a.entries.length){const I=a.entries.reduce(((a,S)=>a.value>S.value?a:S));if(I?.sources?.length){const a=P(I.sources);a&&(S={largestShiftTarget:C.get(a),largestShiftTime:I.startTime,largestShiftValue:I.value,largestShiftSource:a,largestShiftEntry:I,loadState:e(I.startTime)})}}return Object.assign(a,{attribution:S})})(S))}),S)},_=(a,S={})=>{const I=r(S=Object.assign({},S),D);M(S)&&(I.u=new Map);k((S=>{a((a=>{let S={timeToFirstByte:0,firstByteToFCP:a.value,loadState:e(c())};if("soft-navigation"!==a.navigationType){if(a.entries.length){const I=n(),C=a.entries.at(-1);if(I){const L=I.responseStart,x=I.activationStart||0,B=Math.max(0,L-x);S={timeToFirstByte:B,firstByteToFCP:a.value-B,loadState:e(a.entries[0].startTime),navigationEntry:I,fcpEntry:C}}}}else{const C=I.u?.get(a.navigationId);C&&(S={timeToFirstByte:0,firstByteToFCP:a.value,loadState:"complete",navigationEntry:C})}return Object.assign(a,{attribution:S})})(S))}),S)};let H=0,$=1/0,V=0;const O=a=>{for(const S of a)S.interactionId&&($=Math.min($,S.interactionId),V=Math.max(V,S.interactionId),H=V?(V-$)/7+1:0)};let G;const A=()=>G?H:performance.interactionCount??0,N=()=>{"interactionCount"in performance||G||(G=b(["event"],O,{durationThreshold:0}))};class q{h=0;v=[];p=new Map;m;M;T(){return A()-this.h}D(){this.h=A(),this.v.length=0,this.p.clear()}S(a){const S=this.T(),I=Math.min(this.v.length-1,Math.floor(S/50));return!S||-1!==I||"soft-navigation"!==a&&"back-forward-cache"!==a?this.v[I]:{k:8,id:-1,entries:[]}}l(a){if(this.m?.(a),!a.interactionId)return;const S=this.v.at(-1);let I=this.p.get(a.interactionId);if(I||this.v.length<10||a.duration>S.k){if(I?a.duration>I.k?(I.entries=[a],I.k=a.duration):a.duration===I.k&&a.startTime===I.entries[0].startTime&&I.entries.push(a):(I={id:a.interactionId,entries:[a],k:a.duration},this.p.set(I.id,I),this.v.push(I)),this.v.sort(((a,S)=>S.k-a.k)),this.v.length>10){const a=this.v.splice(10);for(const S of a)this.p.delete(S.id)}this.M?.(I)}}}const W=a=>{const S="requestIdleCallback"in globalThis?1e3:0,I=globalThis.requestIdleCallback||setTimeout,C=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)a();else{const L=E(a);let x=-1;const r=()=>{C(x),L()};addEventListener("visibilitychange",r,{once:!0,capture:!0}),x=I((()=>{removeEventListener("visibilitychange",r,{capture:!0}),L()}),{timeout:S})}},Q=[200,500],R=(a,S={})=>{const I=r(S=Object.assign({},S),q);let C=[],L=[],x=0;const B=new WeakMap,j=new WeakMap;let H=!1;const v=()=>{H||(W(p),H=!0)},p=()=>{const a=new Set(I.v.map((a=>B.get(a.entries[0])))),S=L.length-10;L=L.filter(((I,C)=>C>=S||a.has(I)));const j=new Set;for(const a of L){const S=T(a.startTime,a.processingEnd);for(const a of S)j.add(a)}C=C.filter((a=>a.startTime>x||j.has(a))),H=!1};I.m=a=>{const I=a.startTime+a.duration;let C;x=Math.max(x,a.processingEnd);for(let x=L.length-1;x>=0;x--){const B=L[x];if(Math.abs(I-B.renderTime)<=8){C=B,C.startTime=Math.min(a.startTime,C.startTime),C.processingStart=Math.min(a.processingStart,C.processingStart),C.processingEnd=Math.max(a.processingEnd,C.processingEnd),S.includeProcessedEventEntries&&C.entries.push(a);break}}C||(C={startTime:a.startTime,processingStart:a.processingStart,processingEnd:a.processingEnd,renderTime:I,entries:S.includeProcessedEventEntries?[a]:[]},L.push(C)),a.interactionId&&B.set(a,C),v()},I.M=a=>{if(!j.get(a)){const I=a.entries.find((a=>a.target))?.target;if(I){const C=S.generateTarget?.(I)??i(I);j.set(a,C)}else{const S=a.entries.find((a=>a.targetSelector))?.targetSelector;S&&j.set(a,S)}}};const T=(a,S)=>{const I=[];for(const L of C)if(!(L.startTime+L.durationS)break;I.push(L)}return I},E=a=>{if(0===a.entries.length){const S=a.navigationStartTime||0,I={processedEventEntries:[],longAnimationFrameEntries:[],inputDelay:0,processingDuration:0,presentationDelay:a.value,loadState:e(S)};return Object.assign(a,{attribution:I})}const S=a.entries[0],C=B.get(S),L=Math.max(C.processingStart,S.startTime),x=Math.max(S.startTime+S.duration,L),H=Math.min(C.processingEnd,x),$=C.entries.sort(((a,S)=>a.processingStart-S.processingStart)),V=T(S.startTime,H),G=I.p.get(S.interactionId),Q={interactionTarget:j.get(G),interactionType:S.name.startsWith("key")?"keyboard":"pointer",interactionTime:S.startTime,nextPaintTime:x,processedEventEntries:$,longAnimationFrameEntries:V,inputDelay:L-S.startTime,processingDuration:H-L,presentationDelay:x-H,loadState:e(S.startTime),longestScript:void 0,totalScriptDuration:void 0,totalStyleAndLayoutDuration:void 0,totalPaintDuration:void 0,totalUnattributedDuration:void 0};return(a=>{const S=a.interactionTime,I=a.nextPaintTime;if(!a.longAnimationFrameEntries?.length||!S||!I)return;const C=a.inputDelay,L=a.processingDuration;let x,B,j=0,H=0,$=0,V=0;for(const I of a.longAnimationFrameEntries){H=H+I.startTime+I.duration-I.styleAndLayoutStart;for(const a of I.scripts){const I=a.startTime+a.duration;if(IV&&(B=a.startTime=S+C+L?"presentation-delay":"processing-duration",x=a,V=$)}}const G=a.longAnimationFrameEntries.at(-1),Q=G?G.startTime+G.duration:0;Q>=S+C+L&&($=I-Q),x&&B&&(a.longestScript={entry:x,subpart:B,intersectingDuration:V}),a.totalScriptDuration=j,a.totalStyleAndLayoutDuration=H,a.totalPaintDuration=$,a.totalUnattributedDuration=I-S-j-H-$})(Q),Object.assign(a,{attribution:Q})};b(["long-animation-frame"],(a=>{C=C.concat(a),v()}),S),((a,S={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const I=m();w((()=>{N();let C,L=y("INP");const x=r(S,q),s=(I,B,j,H,$)=>{x.D(),L=y("INP",-1,I,B,j,H,$),C=l(a,L,Q,S.reportAllChanges)},u=()=>{const a=x.S(L.navigationType);a&&a.k!==L.value&&(L.value=a.k,L.entries=a.entries,C())},d=a=>{u(),C(!0),s("soft-navigation",a.navigationId,a.interactionId,a.name,a.startTime)},h=(a,S=!1)=>{W((()=>{for(const S of a)"soft-navigation"!==S.entryType?x.l(S):d(S);u(),S&&C(!0)}))},B=["event","first-input"];M(S)&&B.push("soft-navigation");const j=b(B,h,{...S,durationThreshold:S.durationThreshold??40});C=l(a,L,Q,S.reportAllChanges),j&&(I.onHidden((()=>{h(j.takeRecords(),!0)})),f((()=>{s("back-forward-cache",L.navigationId,L.navigationInteractionId,L.navigationURL,c())})))}))})((S=>{a(E(S))}),S)};class U{m;u;l(a){this.m?.(a)}}const X=[2500,4e3];let Y=50;const Z=[];b(["resource"],(a=>{for(const S of a)Z.push(S),Z.length>Y&&Z.shift()}));const z=(a,S={})=>{null!=(S=Object.assign({},S)).resourceBufferSize&&(Y=S.resourceBufferSize);const I=r(S,U),C=new WeakMap;M(S)&&(I.u=new Map),I.m=a=>{const I=a.element;if(I){const L=S.generateTarget?.(I)??i(I);C.set(a,L)}else a.id&&C.set(a,`#${a.id}`)};((a,S={})=>{let I=!1;const C=M(S);w((()=>{let L,x=m(),B=y("LCP");const j=r(S,U),g=(C,j,H,$,V)=>{B=y("LCP",-1,C,j,H,$,V),L=l(a,B,X,S.reportAllChanges),I=!1,"soft-navigation"===C&&(x=m(!0))},v=a=>{j.u&&a.navigationId&&T(j.u,a),I||L(!0),g("soft-navigation",a.navigationId,a.interactionId,a.name,a.startTime);const S=a.getLargestInteractionContentfulPaint?.();S&&p([S])},p=a=>{S.reportAllChanges||C||(a=a.slice(-1));for(const S of a){if(!S)continue;if("soft-navigation"===S.entryType){v(S);continue}let a=0,I=[],C=S.startTime;if("largest-contentful-paint"===S.entryType)a=Math.max(S.startTime-d(),0),j.l(S),I=[S];else if("interaction-contentful-paint"===S.entryType){const L=S;if(!B.navigationId)continue;if("interactionId"in L&&L.interactionId!=B.navigationInteractionId)continue;C=L.largestContentfulPaint?.renderTime||0,a=Math.max(C-S.startTime,0),L.largestContentfulPaint&&(j.l(L.largestContentfulPaint),I=[L.largestContentfulPaint])}C{if(a.isTrusted&&!I){const a=B.id;W((()=>{if(!I){if(!C){$.disconnect();for(const a of x)removeEventListener(a,r,{capture:!0})}a===B.id&&(I=!0,L(!0))}}))}};for(const a of x)addEventListener(a,r,{capture:!0});f((C=>{g("back-forward-cache",B.navigationId,B.navigationInteractionId,B.navigationURL,c()),L=l(a,B,X,S.reportAllChanges),u((()=>{B.value=performance.now()-C.timeStamp,I=!0,L(!0)}))}))}}))})((S=>{a((a=>{let S={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadDuration:0,elementRenderDelay:a.value};if(a.entries.length){const L=a.entries.at(-1),x=L.url&&(Z.findLast((a=>a.name===L.url))||performance.getEntriesByType("resource").findLast((a=>a.name===L.url)));let B;S.target=C.get(L),S.lcpEntry=L,L.url&&(S.url=L.url),x&&(S.lcpResourceEntry=x);let j=0,H=0;if("soft-navigation"!==a.navigationType?(B=n(),j=B?.activationStart??0,H=B?.responseStart??0):(j=a.navigationStartTime||0,B=I.u?.get(a.navigationId)),B){const I=Math.max(0,H-j),C=Math.max(I,x?(x.requestStart||x.startTime)-j:0),L=Math.min(a.value,Math.max(C,x?x.responseEnd-j:0));S={...S,timeToFirstByte:I,resourceLoadDelay:C-I,resourceLoadDuration:L-C,elementRenderDelay:a.value-L,navigationEntry:B}}}return Object.assign(a,{attribution:S})})(S))}),S)},tt=[800,1800],J=a=>{document.prerendering?w((()=>J(a))):"complete"!==document.readyState?addEventListener("load",(()=>J(a)),!0):setTimeout(a)},K=(a,S={})=>{((a,S={})=>{const I=M(S);let C=y("TTFB"),L=l(a,C,tt,S.reportAllChanges);J((()=>{const x=n();if(x){const B=x.responseStart;C.value=Math.max(B-d(),0),C.entries=[x],L(!0),f((()=>{C=y("TTFB",0,"back-forward-cache",C.navigationId,C.navigationInteractionId,C.navigationURL,c()),L=l(a,C,tt,S.reportAllChanges),L(!0)})),I&&b(["soft-navigation"],(I=>{I.forEach((I=>{I.navigationId&&(C=y("TTFB",0,"soft-navigation",I.navigationId,I.interactionId,I.name,I.startTime),C.entries=[I],L=l(a,C,tt,S.reportAllChanges),L(!0))}))}),S)}}))})((S=>{a((a=>{const S=a.entries[0];let I={waitingDuration:0,cacheDuration:0,dnsDuration:0,connectionDuration:0,requestDuration:0,navigationEntry:S};if(a.entries.length&&S instanceof PerformanceNavigationTiming){const C=S.activationStart||0,L=Math.max((S.workerStart||S.fetchStart||0)-C,0),x=Math.max(S.domainLookupStart-C,0),B=Math.max(S.connectStart-C,0),j=Math.max(S.connectEnd-C,0);I={waitingDuration:L,cacheDuration:x-L,dnsDuration:B-x,connectionDuration:j-B,requestDuration:a.value-j,navigationEntry:S}}return Object.assign(a,{attribution:I})})(S))}),S)};module.exports=S})(); \ No newline at end of file diff --git a/packages/next/src/compiled/web-vitals/web-vitals.js b/packages/next/src/compiled/web-vitals/web-vitals.js index c5727ba1ad0e..e01cb2bee1a9 100644 --- a/packages/next/src/compiled/web-vitals/web-vitals.js +++ b/packages/next/src/compiled/web-vitals/web-vitals.js @@ -1 +1 @@ -(function(){"use strict";var n={};!function(){n.d=function(b,L){for(var P in L){if(n.o(L,P)&&!n.o(b,P)){Object.defineProperty(b,P,{enumerable:true,get:L[P]})}}}}();!function(){n.o=function(n,b){return Object.prototype.hasOwnProperty.call(n,b)}}();!function(){n.r=function(n){if(typeof Symbol!=="undefined"&&Symbol.toStringTag){Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}Object.defineProperty(n,"__esModule",{value:true})}}();if(typeof n!=="undefined")n.ab=__dirname+"/";var b={};n.r(b);n.d(b,{CLSThresholds:function(){return j},FCPThresholds:function(){return B},FIDThresholds:function(){return cn},INPThresholds:function(){return nn},LCPThresholds:function(){return en},TTFBThresholds:function(){return rn},onCLS:function(){return w},onFCP:function(){return S},onFID:function(){return $},onINP:function(){return N},onLCP:function(){return z},onTTFB:function(){return K}});var L,P,I,A,F,D=-1,a=function(n){addEventListener("pageshow",(function(b){b.persisted&&(D=b.timeStamp,n(b))}),!0)},c=function(){var n=self.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0];if(n&&n.responseStart>0&&n.responseStart=0?P="back-forward-cache":L&&(document.prerendering||u()>0?P="prerender":document.wasDiscarded?P="restore":L.type&&(P=L.type.replace(/_/g,"-")));return{name:n,value:void 0===b?-1:b,rating:"good",delta:0,entries:[],id:"v4-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:P}},s=function(n,b,L){try{if(PerformanceObserver.supportedEntryTypes.includes(n)){var P=new PerformanceObserver((function(n){Promise.resolve().then((function(){b(n.getEntries())}))}));return P.observe(Object.assign({type:n,buffered:!0},L||{})),P}}catch(n){}},d=function(n,b,L,P){var I,A;return function(F){b.value>=0&&(F||P)&&((A=b.value-(I||0))||void 0===I)&&(I=b.value,b.delta=A,b.rating=function(n,b){return n>b[1]?"poor":n>b[0]?"needs-improvement":"good"}(b.value,L),n(b))}},l=function(n){requestAnimationFrame((function(){return requestAnimationFrame((function(){return n()}))}))},p=function(n){document.addEventListener("visibilitychange",(function(){"hidden"===document.visibilityState&&n()}))},v=function(n){var b=!1;return function(){b||(n(),b=!0)}},O=-1,h=function(){return"hidden"!==document.visibilityState||document.prerendering?1/0:0},g=function(n){"hidden"===document.visibilityState&&O>-1&&(O="visibilitychange"===n.type?n.timeStamp:0,T())},y=function(){addEventListener("visibilitychange",g,!0),addEventListener("prerenderingchange",g,!0)},T=function(){removeEventListener("visibilitychange",g,!0),removeEventListener("prerenderingchange",g,!0)},E=function(){return O<0&&(O=h(),y(),a((function(){setTimeout((function(){O=h(),y()}),0)}))),{get firstHiddenTime(){return O}}},C=function(n){document.prerendering?addEventListener("prerenderingchange",(function(){return n()}),!0):n()},B=[1800,3e3],S=function(n,b){b=b||{},C((function(){var L,P=E(),I=f("FCP"),A=s("paint",(function(n){n.forEach((function(n){"first-contentful-paint"===n.name&&(A.disconnect(),n.startTimeP.value&&(P.value=I,P.entries=A,L())},F=s("layout-shift",c);F&&(L=d(n,P,j,b.reportAllChanges),p((function(){c(F.takeRecords()),L(!0)})),a((function(){I=0,P=f("CLS",0),L=d(n,P,j,b.reportAllChanges),l((function(){return L()}))})),setTimeout(L,0))})))},x=0,_=1/0,G=0,M=function(n){n.forEach((function(n){n.interactionId&&(_=Math.min(_,n.interactionId),G=Math.max(G,n.interactionId),x=G?(G-_)/7+1:0)}))},k=function(){"interactionCount"in performance||L||(L=s("event",M,{type:"event",buffered:!0,durationThreshold:0}))},J=[],Q=new Map,U=0,R=function(){return(L?x:performance.interactionCount||0)-U},Z=[],H=function(n){if(Z.forEach((function(b){return b(n)})),n.interactionId||"first-input"===n.entryType){var b=J[J.length-1],L=Q.get(n.interactionId);if(L||J.length<10||n.duration>b.latency){if(L)n.duration>L.latency?(L.entries=[n],L.latency=n.duration):n.duration===L.latency&&n.startTime===L.entries[0].startTime&&L.entries.push(n);else{var P={id:n.interactionId,latency:n.duration,entries:[n]};Q.set(P.id,P),J.push(P)}J.sort((function(n,b){return b.latency-n.latency})),J.length>10&&J.splice(10).forEach((function(n){return Q.delete(n.id)}))}}},q=function(n){var b=self.requestIdleCallback||self.setTimeout,L=-1;return n=v(n),"hidden"===document.visibilityState?n():(L=b(n),p(n)),L},nn=[200,500],N=function(n,b){"PerformanceEventTiming"in self&&"interactionId"in PerformanceEventTiming.prototype&&(b=b||{},C((function(){var L;k();var P,I=f("INP"),o=function(n){q((function(){n.forEach(H);var b,L=(b=Math.min(J.length-1,Math.floor(R()/50)),J[b]);L&&L.latency!==I.value&&(I.value=L.latency,I.entries=L.entries,P())}))},A=s("event",o,{durationThreshold:null!==(L=b.durationThreshold)&&void 0!==L?L:40});P=d(n,I,nn,b.reportAllChanges),A&&(A.observe({type:"first-input",buffered:!0}),p((function(){o(A.takeRecords()),P(!0)})),a((function(){U=0,J.length=0,Q.clear(),I=f("INP"),P=d(n,I,nn,b.reportAllChanges)})))})))},en=[2500,4e3],tn={},z=function(n,b){b=b||{},C((function(){var L,P=E(),I=f("LCP"),o=function(n){b.reportAllChanges||(n=n.slice(-1)),n.forEach((function(n){n.startTime=0&&I1e12?new Date:performance.now())-n.timeStamp;"pointerdown"==n.type?function(n,b){var t=function(){V(n,b),i()},r=function(){i()},i=function(){removeEventListener("pointerup",t,an),removeEventListener("pointercancel",r,an)};addEventListener("pointerup",t,an),addEventListener("pointercancel",r,an)}(b,n):V(b,n)}},Y=function(n){["mousedown","keydown","touchstart","pointerdown"].forEach((function(b){return n(b,X,an)}))},cn=[100,300],$=function(n,b){b=b||{},C((function(){var L,A=E(),D=f("FID"),l=function(n){n.startTimec,e=t=>{addEventListener("pageshow",(r=>{r.persisted&&(c=r.timeStamp,t(r))}),!0)},i=(t,r,c,I)=>{let L,w;return E=>{r.value>=0&&(E||I)&&(w=r.value-(L??0),(w||void 0===L)&&(L=r.value,r.delta=w,r.rating=((t,r)=>t>r[1]?"poor":t>r[0]?"needs-improvement":"good")(r.value,c),t(r)))}},o=t=>{requestAnimationFrame((()=>requestAnimationFrame((()=>t()))))},s=()=>{const t=performance.getEntriesByType("navigation")[0];if(t&&t.responseStart>0&&t.responseStarts()?.activationStart??0;let I=-1;const L=new Set,f=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,h=t=>{if("hidden"===document.visibilityState){if("visibilitychange"===t.type)for(const t of L)t();isFinite(I)||(I="visibilitychange"===t.type?t.timeStamp:0,removeEventListener("prerenderingchange",h,!0))}},d=(t=!1)=>{if(t&&(I=1/0),I<0){const t=a(),r=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").find((r=>"hidden"===r.name&&r.startTime>=t))?.startTime;I=r??f(),addEventListener("visibilitychange",h,!0),addEventListener("prerenderingchange",h,!0),e((()=>{setTimeout((()=>{I=f()}))}))}return{get firstHiddenTime(){return I},onHidden(t){L.add(t)}}},l=(t,r=-1,c,I=0,L,w,E)=>{const k=s(),A=k?.navigationId||0;let F="navigate";c?F=c:n()>=0?F="back-forward-cache":k&&(document.prerendering||a()>0?F="prerender":document.wasDiscarded?F="restore":k.type&&(F=k.type.replace(/_/g,"-")));return{name:t,value:r,rating:"good",delta:0,entries:[],id:`v6-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:F,navigationId:I||A,navigationInteractionId:L,navigationURL:w||k?.name,navigationStartTime:E||0}},w=new WeakMap;function u(t,r){let c=w.get(r);return c||(c=new WeakMap,w.set(r,c)),c.get(t)||c.set(t,new r),c.get(t)}class v{t;i=0;o=[];h(t){if(t.hadRecentInput)return;const r=this.o[0],c=this.o.at(-1);this.i&&r&&c&&t.startTime-c.startTime<1e3&&t.startTime-r.startTime<5e3?(this.i+=t.value,this.o.push(t)):(this.i=t.value,this.o=[t]),this.t?.(t)}}const m=(t,r,c={})=>{try{const I=t.filter((t=>PerformanceObserver.supportedEntryTypes.includes(t)));if(I.length>0){const t=new PerformanceObserver((t=>{queueMicrotask((()=>{const c=t.getEntries();I.length>1&&c.sort(((t,r)=>t.startTime+t.duration-(r.startTime+r.duration))),r(c)}))}));for(const r of I)t.observe({type:r,buffered:!0,...c});return t}}catch{}},p=t=>globalThis.PerformanceObserver?.supportedEntryTypes?.includes("soft-navigation")&&"function"==typeof globalThis.PerformanceSoftNavigation?.prototype?.getLargestInteractionContentfulPaint&&t&&t.reportSoftNavs,b=(t,r)=>{if(t.set(r.navigationId,r),t.size>2){const r=t.keys().next().value;void 0!==r&&t.delete(r)}},T=t=>{let r=!1;return()=>{r||(t(),r=!0)}};class y{l}const _=t=>{document.prerendering?addEventListener("prerenderingchange",t,!0):t()},E=[1800,3e3],M=(t,r={})=>{const c=p(r);_((()=>{const I=u(r,y),L=d();let w,k=l("FCP");const A=m(["paint"],(t=>{for(const r of t)"first-contentful-paint"===r.name&&(A.disconnect(),r.startTime{k=l("FCP",-1,"back-forward-cache",k.navigationId,k.navigationInteractionId,k.navigationURL,n()),w=i(t,k,E,r.reportAllChanges),o((()=>{k.value=performance.now()-c.timeStamp,w(!0)}))}))),c){m(["soft-navigation"],(c=>{c.forEach((c=>{I.l&&c.navigationId&&b(I.l,c);const L=Math.max((c.presentationTime||c.paintTime||0)-c.startTime,0);k=l("FCP",L,"soft-navigation",c.navigationId,c.interactionId,c.name,c.startTime),w=i(t,k,E,r.reportAllChanges),w(!0)}))}),r)}}))},k=[.1,.25],P=(t,r={})=>{const c=d();M(T((()=>{let I,L=l("CLS",0);const w=u(r,v),h=(c,E,A,F,R)=>{L=l("CLS",0,c,E,A,F,R),w.i=0,I=i(t,L,k,r.reportAllChanges)},d=(t=!1)=>{w.i>L.value&&(L.value=w.i,L.entries=w.o),I(t)},g=t=>{d(!0),h("soft-navigation",t.navigationId,t.interactionId,t.name,t.startTime)},b=t=>{for(const r of t)"soft-navigation"!==r.entryType?w.h(r):g(r);d()},E=["layout-shift"];p(r)&&E.push("soft-navigation");const A=m(E,b);A&&(I=i(t,L,k,r.reportAllChanges),c.onHidden((()=>{b(A.takeRecords()),I(!0)})),e((()=>{h("back-forward-cache",L.navigationId,L.navigationInteractionId,L.navigationURL,n()),o(I)})),setTimeout(I))})))};let A=0,F=1/0,R=0;const C=t=>{for(const r of t)r.interactionId&&(F=Math.min(F,r.interactionId),R=Math.max(R,r.interactionId),A=R?(R-F)/7+1:0)};let O;const N=()=>O?A:performance.interactionCount??0,B=()=>{"interactionCount"in performance||O||(O=m(["event"],C,{durationThreshold:0}))};class S{u=0;v=[];m=new Map;p;T;_(){return N()-this.u}M(){this.u=N(),this.v.length=0,this.m.clear()}L(t){const r=this._(),c=Math.min(this.v.length-1,Math.floor(r/50));return!r||-1!==c||"soft-navigation"!==t&&"back-forward-cache"!==t?this.v[c]:{P:8,id:-1,entries:[]}}h(t){if(this.p?.(t),!t.interactionId)return;const r=this.v.at(-1);let c=this.m.get(t.interactionId);if(c||this.v.length<10||t.duration>r.P){if(c?t.duration>c.P?(c.entries=[t],c.P=t.duration):t.duration===c.P&&t.startTime===c.entries[0].startTime&&c.entries.push(t):(c={id:t.interactionId,entries:[t],P:t.duration},this.m.set(c.id,c),this.v.push(c)),this.v.sort(((t,r)=>r.P-t.P)),this.v.length>10){const t=this.v.splice(10);for(const r of t)this.m.delete(r.id)}this.T?.(c)}}}const q=t=>{const r="requestIdleCallback"in globalThis?1e3:0,c=globalThis.requestIdleCallback||setTimeout,I=globalThis.cancelIdleCallback||clearTimeout;if("hidden"===document.visibilityState)t();else{const L=T(t);let w=-1;const a=()=>{I(w),L()};addEventListener("visibilitychange",a,{once:!0,capture:!0}),w=c((()=>{removeEventListener("visibilitychange",a,{capture:!0}),L()}),{timeout:r})}},j=[200,500],x=(t,r={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const c=d();_((()=>{B();let I,L=l("INP");const w=u(r,S),f=(c,E,k,A,F)=>{w.M(),L=l("INP",-1,c,E,k,A,F),I=i(t,L,j,r.reportAllChanges)},h=()=>{const t=w.L(L.navigationType);t&&t.P!==L.value&&(L.value=t.P,L.entries=t.entries,I())},d=t=>{h(),I(!0),f("soft-navigation",t.navigationId,t.interactionId,t.name,t.startTime)},g=(t,r=!1)=>{q((()=>{for(const r of t)"soft-navigation"!==r.entryType?w.h(r):d(r);h(),r&&I(!0)}))},E=["event","first-input"];p(r)&&E.push("soft-navigation");const k=m(E,g,{...r,durationThreshold:r.durationThreshold??40});I=i(t,L,j,r.reportAllChanges),k&&(c.onHidden((()=>{g(k.takeRecords(),!0)})),e((()=>{f("back-forward-cache",L.navigationId,L.navigationInteractionId,L.navigationURL,n())})))}))};class H{p;l;h(t){this.p?.(t)}}const W=[2500,4e3],U=(t,r={})=>{let c=!1;const I=p(r);_((()=>{let L,w=d(),E=l("LCP");const k=u(r,H),p=(I,k,A,F,R)=>{E=l("LCP",-1,I,k,A,F,R),L=i(t,E,W,r.reportAllChanges),c=!1,"soft-navigation"===I&&(w=d(!0))},T=t=>{k.l&&t.navigationId&&b(k.l,t),c||L(!0),p("soft-navigation",t.navigationId,t.interactionId,t.name,t.startTime);const r=t.getLargestInteractionContentfulPaint?.();r&&y([r])},y=t=>{r.reportAllChanges||I||(t=t.slice(-1));for(const r of t){if(!r)continue;if("soft-navigation"===r.entryType){T(r);continue}let t=0,c=[],I=r.startTime;if("largest-contentful-paint"===r.entryType)t=Math.max(r.startTime-a(),0),k.h(r),c=[r];else if("interaction-contentful-paint"===r.entryType){const L=r;if(!E.navigationId)continue;if("interactionId"in L&&L.interactionId!=E.navigationInteractionId)continue;I=L.largestContentfulPaint?.renderTime||0,t=Math.max(I-r.startTime,0),L.largestContentfulPaint&&(k.h(L.largestContentfulPaint),c=[L.largestContentfulPaint])}I{if(t.isTrusted&&!c){const t=E.id;q((()=>{if(!c){if(!I){F.disconnect();for(const t of w)removeEventListener(t,h,{capture:!0})}t===E.id&&(c=!0,L(!0))}}))}};for(const t of w)addEventListener(t,h,{capture:!0});e((I=>{p("back-forward-cache",E.navigationId,E.navigationInteractionId,E.navigationURL,n()),L=i(t,E,W,r.reportAllChanges),o((()=>{E.value=performance.now()-I.timeStamp,c=!0,L(!0)}))}))}}))},z=[800,1800],$=t=>{document.prerendering?_((()=>$(t))):"complete"!==document.readyState?addEventListener("load",(()=>$(t)),!0):setTimeout(t)},D=(t,r={})=>{const c=p(r);let I=l("TTFB"),L=i(t,I,z,r.reportAllChanges);$((()=>{const w=s();if(w){const E=w.responseStart;if(I.value=Math.max(E-a(),0),I.entries=[w],L(!0),e((()=>{I=l("TTFB",0,"back-forward-cache",I.navigationId,I.navigationInteractionId,I.navigationURL,n()),L=i(t,I,z,r.reportAllChanges),L(!0)})),c){m(["soft-navigation"],(c=>{c.forEach((c=>{c.navigationId&&(I=l("TTFB",0,"soft-navigation",c.navigationId,c.interactionId,c.name,c.startTime),I.entries=[c],L=i(t,I,z,r.reportAllChanges),L(!0))}))}),r)}}}))};module.exports=r})(); \ No newline at end of file diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 1cb970fee817..9aa4cc61418b 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -346,7 +346,6 @@ export const experimentalSchema = { z.union([ z.literal('CLS'), z.literal('FCP'), - z.literal('FID'), z.literal('INP'), z.literal('LCP'), z.literal('TTFB'), diff --git a/packages/next/src/shared/lib/utils.ts b/packages/next/src/shared/lib/utils.ts index d5b36412bc36..77205df09ff7 100644 --- a/packages/next/src/shared/lib/utils.ts +++ b/packages/next/src/shared/lib/utils.ts @@ -42,7 +42,7 @@ export type AppTreeType = ComponentType< * Web vitals provided to _app.reportWebVitals by Core Web Vitals plugin developed by Google Chrome team. * https://nextjs.org/blog/next-9-4#integrated-web-vitals-reporting */ -export const WEB_VITALS = ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'] as const +export const WEB_VITALS = ['CLS', 'FCP', 'INP', 'LCP', 'TTFB'] as const export type NextWebVitalsMetric = { id: string startTime: number diff --git a/patches/web-vitals@4.2.1.patch b/patches/web-vitals@4.2.1.patch deleted file mode 100644 index 34560a4439b8..000000000000 --- a/patches/web-vitals@4.2.1.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/dist/modules/types.d.ts b/dist/modules/types.d.ts -index 080d3667303398f9ee8cf782c43f2230ea72ef53..cdfe14ad4bdbe07023301dae36e7ea64a5048bf4 100644 ---- a/dist/modules/types.d.ts -+++ b/dist/modules/types.d.ts -@@ -27,7 +27,8 @@ declare global { - } - interface PerformanceEventTiming extends PerformanceEntry { - duration: DOMHighResTimeStamp; -- interactionId: number; -+ // Waiting for https://github.com/GoogleChrome/web-vitals/commit/a92f8a78bce9e400366bc29db5fc23fdf23db469 -+ readonly interactionId: number; - } - interface LayoutShiftAttribution { - node?: Node; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c81cf06bd3b4..438b05478bd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,9 +53,6 @@ patchedDependencies: taskr@1.1.0: hash: 1d0f571943b5d4761d4f2f39723796e402dd317f14a7c714e2021d3f12cc481c path: patches/taskr@1.1.0.patch - web-vitals@4.2.1: - hash: f4d0a6719cc28520df7c47a0e2c2641759c2a0ad06ad57868c4c9d0567d4276d - path: patches/web-vitals@4.2.1.patch webpack-sources@3.2.3: hash: 26afc15966a3fc37a3d1d366312a95ce66723513f6a3a720e4166a37147da8bd path: patches/webpack-sources@3.2.3.patch @@ -1786,8 +1783,8 @@ importers: specifier: 2.4.0 version: 2.4.0 web-vitals: - specifier: 4.2.1 - version: 4.2.1(patch_hash=f4d0a6719cc28520df7c47a0e2c2641759c2a0ad06ad57868c4c9d0567d4276d) + specifier: 6.2.1 + version: 6.2.1 webpack: specifier: 5.98.0 version: 5.98.0(@swc/core@1.11.24(@swc/helpers@0.5.23))(esbuild@0.25.9) @@ -18550,8 +18547,8 @@ packages: web-namespaces@1.1.4: resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==} - web-vitals@4.2.1: - resolution: {integrity: sha512-U6bAxeudnhDqcXNl50JC4hLlqox9DZnngxfISZm3DMZnonW35xtJOVUc091L+DOY+6hVZVpKXoiCP0RiT6339Q==} + web-vitals@6.2.1: + resolution: {integrity: sha512-rLcLXA2sx6+9dE88NHFubwTtGxpK4yYBLj6qHPdFoCaLr0cXGb4efOqtKLlm4loGA4OEKHIQKMKzZkKyOh5ctw==} web-worker@1.3.0: resolution: {integrity: sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==} @@ -38553,7 +38550,7 @@ snapshots: web-namespaces@1.1.4: {} - web-vitals@4.2.1(patch_hash=f4d0a6719cc28520df7c47a0e2c2641759c2a0ad06ad57868c4c9d0567d4276d): {} + web-vitals@6.2.1: {} web-worker@1.3.0: {} 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 index 3b12807859d0..907491c97a3a 100644 --- a/test/e2e/app-dir/app/app/report-web-vitals/layout.js +++ b/test/e2e/app-dir/app/app/report-web-vitals/layout.js @@ -1,16 +1,27 @@ 'use client' import { useState, useEffect } from 'react' +import Reporter from './reporter' export default function ClientNestedLayout({ children }) { const [count, setCount] = useState(0) useEffect(() => { setCount(1) }, []) + + function handleClick() { + const start = performance.now() + while (performance.now() - start < 200) { + // Ensure this interaction exceeds web-vitals' INP duration threshold. + } + setCount(count + 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 index fc54c58899e2..1c324d1c3105 100644 --- a/test/e2e/app-dir/app/app/report-web-vitals/page.js +++ b/test/e2e/app-dir/app/app/report-web-vitals/page.js @@ -1,10 +1,3 @@ -import Reporter from './reporter' - export default function component() { - return ( - <> -

Test

- - - ) + return

Test

} diff --git a/test/e2e/app-dir/app/useReportWebVitals.test.ts b/test/e2e/app-dir/app/useReportWebVitals.test.ts index a28457b7e60a..a43b5d4c58ab 100644 --- a/test/e2e/app-dir/app/useReportWebVitals.test.ts +++ b/test/e2e/app-dir/app/useReportWebVitals.test.ts @@ -1,5 +1,6 @@ import { nextTestSetup } from 'e2e-utils' -import { check } from 'next-test-utils' +import { retry } from 'next-test-utils' +import type { Page } from 'playwright' describe('useReportWebVitals hook', () => { const { next } = nextTestSetup({ @@ -15,30 +16,45 @@ describe('useReportWebVitals hook', () => { await next.start() }) + function collectWebVitals(page: Page, events: Array>) { + return page.route('https://example.vercel.sh/vitals', async (route) => { + events.push( + Object.fromEntries( + new URLSearchParams(route.request().postData() ?? '') + ) + ) + await route.fulfill() + }) + } + // Analytics events are only sent in production it('should send web-vitals', async () => { await next.fetch('/report-web-vitals') - let eventsCount = 0 + const events: Array> = [] const browser = await next.browser('/report-web-vitals', { - beforePageLoad: (page) => { - page.route('https://example.vercel.sh/vitals', (route) => { - eventsCount += 1 - route.fulfill() - }) + beforePageLoad: async (currentPage) => { + await collectWebVitals(currentPage, events) }, }) - // Refresh will trigger CLS and LCP. When page loads FCP and TTFB will trigger: + // Refresh will report another set of navigation metrics. await browser.refresh() - // After interaction LCP and FID will trigger + // Exercise the interaction reporting path. await browser.elementById('btn').click() // Make sure all registered events in performance-relayer has fired - await check(async () => { - expect(eventsCount).toBeGreaterThanOrEqual(6) - return 'success' - }, 'success') + await retry(() => { + expect(events.length).toBeGreaterThanOrEqual(4) + }) + expect(events.map((event) => event.name)).not.toContain('FID') + expect(events).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + navigationURL: expect.stringMatching(/\/report-web-vitals$/), + }), + ]) + ) }) })