rsnext/packages/next/client/performance-relayer.ts
Keen Yee Liau dc36199b22
add method to measure Interaction to Next Paint (INP) (#36490)
This commit lets users measure their Interaction to Next Paint [INP](https://web.dev/inp/) web vital.
Note that the `web-vitals` package is beta to denote that INP is an experimental metric, the code is stable and v3 is backwards compatible.

    `web-vitals` CHANGELOG for v3:
    
    - [BREAKING] Report TTFB after a bfcache restore
    - [BREAKING] Only include last LCP entry in metric entries
    - Add support for the new INP metric
    - Rename getXXX() functions to onXXX()
    - Add a navigationType property to the Metric object
    
    See https://github.com/GoogleChrome/web-vitals/blob/next/CHANGELOG.md

Upgraded `playwright-chromium` from `1.14.1` to `1.17.2` because the Events Timing API used to measure INP is only available in Chromium >= v98.



## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `yarn lint`
2022-06-07 18:28:58 +00:00

91 lines
2.5 KiB
TypeScript

/* global location */
import {
onCLS,
onFCP,
onFID,
onINP,
onLCP,
onTTFB,
Metric,
ReportCallback,
} from 'next/dist/compiled/web-vitals'
const initialHref = location.href
let isRegistered = false
let userReportHandler: ReportCallback | undefined
function onReport(metric: Metric): void {
if (userReportHandler) {
userReportHandler(metric)
}
// This code is not shipped, executed, or present in the client-side
// JavaScript bundle unless explicitly enabled in your application.
//
// When this feature is enabled, we'll make it very clear by printing a
// message during the build (`next build`).
if (
process.env.NODE_ENV === 'production' &&
// This field is empty unless you explicitly configure it:
process.env.__NEXT_ANALYTICS_ID
) {
const body: Record<string, string> = {
dsn: process.env.__NEXT_ANALYTICS_ID,
id: metric.id,
page: window.__NEXT_DATA__.page,
href: initialHref,
event_name: metric.name,
value: metric.value.toString(),
speed:
'connection' in navigator &&
(navigator as any)['connection'] &&
'effectiveType' in (navigator as any)['connection']
? ((navigator as any)['connection']['effectiveType'] as string)
: '',
}
const blob = new Blob([new URLSearchParams(body).toString()], {
// This content type is necessary for `sendBeacon`:
type: 'application/x-www-form-urlencoded',
})
const vitalsUrl = 'https://vitals.vercel-insights.com/v1/vitals'
// Navigator has to be bound to ensure it does not error in some browsers
// https://xgwang.me/posts/you-may-not-know-beacon/#it-may-throw-error%2C-be-sure-to-catch
const send = navigator.sendBeacon && navigator.sendBeacon.bind(navigator)
function fallbackSend() {
fetch(vitalsUrl, {
body: blob,
method: 'POST',
credentials: 'omit',
keepalive: true,
// console.error is used here as when the fetch fails it does not affect functioning of the app
}).catch(console.error)
}
try {
// If send is undefined it'll throw as well. This reduces output code size.
send!(vitalsUrl, blob) || fallbackSend()
} catch (err) {
fallbackSend()
}
}
}
export default (onPerfEntry?: ReportCallback): void => {
// Update function if it changes:
userReportHandler = onPerfEntry
// Only register listeners once:
if (isRegistered) {
return
}
isRegistered = true
onCLS(onReport)
onFID(onReport)
onFCP(onReport)
onLCP(onReport)
onTTFB(onReport)
onINP(onReport)
}