Skip to content

Commit

Permalink
make web vitals attribution experimental
Browse files Browse the repository at this point in the history
  • Loading branch information
kyliau committed Oct 3, 2022
1 parent 6d05e44 commit cc51c1e
Show file tree
Hide file tree
Showing 9 changed files with 41 additions and 39 deletions.
18 changes: 4 additions & 14 deletions docs/advanced-features/measuring-performance.md
Expand Up @@ -191,23 +191,13 @@ If the LCP element is an image, knowing the URL of the image resource can help u
Pinpointing the biggest contributor to the Web Vitals score, aka [attribution](https://github.com/GoogleChrome/web-vitals/blob/4ca38ae64b8d1e899028c692f94d4c56acfc996c/README.md#attribution),
allows us to obtain more in-depth information like entries for [PerformanceEventTiming](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEventTiming), [PerformanceNavigationTiming](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming) and [PerformanceResourceTiming](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming).

Attribution is disabled by default in Next.js but can be enabled **per metric** by specifying a `config` for `reportWebVitals()`.
Attribution is disabled by default in Next.js but can be enabled **per metric** by specifying the following in `next.config.js`.

```js
// pages/_app.js
export function reportWebVitals(metric) {
console.log(metric)
// next.config.js
experimental: {
webVitalsAttribution: ['CLS', 'LCP']
}

reportWebVitals.config = {
attributions: ['CLS', 'LCP'],
}

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}

export default MyApp
```

Valid attribution values are all `web-vitals` metrics specified in the [`NextWebVitalsMetric`](https://github.com/vercel/next.js/blob/442378d21dd56d6e769863eb8c2cb521a463a2e0/packages/next/shared/lib/utils.ts#L43) type.
Expand Down
3 changes: 3 additions & 0 deletions packages/next/build/webpack-config.ts
Expand Up @@ -253,6 +253,9 @@ export function getDefineEnv({
'process.env.__NEXT_I18N_SUPPORT': JSON.stringify(!!config.i18n),
'process.env.__NEXT_I18N_DOMAINS': JSON.stringify(config.i18n?.domains),
'process.env.__NEXT_ANALYTICS_ID': JSON.stringify(config.analyticsId),
'process.env.__NEXT_WEB_VITALS_ATTRIBUTION': JSON.stringify(
config.experimental.webVitalsAttribution
),
...(isNodeServer || isEdgeServer
? {
// Fix bad-actors in the npm ecosystem (e.g. `node-formidable`)
Expand Down
11 changes: 9 additions & 2 deletions packages/next/client/index.tsx
Expand Up @@ -767,8 +767,15 @@ export async function hydrate(opts?: { beforeRender?: () => Promise<void> }) {
const { component: app, exports: mod } = appEntrypoint
CachedApp = app as AppComponent
if (mod && mod.reportWebVitals) {
if (mod.reportWebVitals.config) {
setPerformanceRelayerConfig(mod.reportWebVitals.config)
if (process.env.__NEXT_WEB_VITALS_ATTRIBUTION) {
try {
const attributions = JSON.parse(
process.env.__NEXT_WEB_VITALS_ATTRIBUTION
)
if (Array.isArray(attributions)) {
setPerformanceRelayerConfig(attributions)
}
} catch {}
}
onPerfEntry = ({
id,
Expand Down
26 changes: 8 additions & 18 deletions packages/next/client/performance-relayer.ts
@@ -1,15 +1,12 @@
/* global location */
import { Metric, ReportCallback } from 'next/dist/compiled/web-vitals'
import { WEB_VITALS } from '../shared/lib/utils'

interface PerformanceRelayerConfig {
attributions: Array<typeof WEB_VITALS[number]>
}

const WEB_VITALS = ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'] as const
const initialHref = location.href
let isRegistered = false
let userReportHandler: ReportCallback | undefined
let config: PerformanceRelayerConfig | undefined
type Attribution = typeof WEB_VITALS[number]
let attributions: Attribution[] = []

function onReport(metric: Metric): void {
if (userReportHandler) {
Expand Down Expand Up @@ -81,10 +78,10 @@ export default async (onPerfEntry?: ReportCallback): Promise<void> => {

for (const webVital of WEB_VITALS) {
try {
const m: any = config?.attributions.includes(webVital)
? // @ts-ignore module available at runtime, see taskfile.js
const m: any = attributions.includes(webVital)
? // @ts-ignore package.json created by ncc doesn't have types
await import('../compiled/web-vitals-attribution')
: // @ts-ignore module available at runtime, see taskfile.js
: // @ts-ignore package.json created by ncc doesn't have types
await import('../compiled/web-vitals')
m[`on${webVital}`](onReport)
} catch {
Expand All @@ -93,13 +90,6 @@ export default async (onPerfEntry?: ReportCallback): Promise<void> => {
}
}

export function setPerformanceRelayerConfig(
input: PerformanceRelayerConfig
): void {
const attributions = Array.isArray(input.attributions)
? input.attributions
: []
config = {
attributions: attributions.filter((attr) => WEB_VITALS.includes(attr)),
}
export function setPerformanceRelayerConfig(input: Attribution[]): void {
attributions = input.filter((v) => WEB_VITALS.includes(v))
}
7 changes: 7 additions & 0 deletions packages/next/server/config-schema.ts
Expand Up @@ -402,6 +402,13 @@ const configSchema = {
fontLoaders: {
type: 'object',
},
webVitalsAttribution: {
type: 'array',
items: {
type: 'string',
enum: ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'],
} as any,
},
},
type: 'object',
},
Expand Down
3 changes: 3 additions & 0 deletions packages/next/server/config-shared.ts
Expand Up @@ -8,6 +8,7 @@ import {
} from '../shared/lib/image-config'
import { ServerRuntime } from 'next/types'
import { SubresourceIntegrityAlgorithm } from '../build/webpack/plugins/subresource-integrity-plugin'
import { WEB_VITALS } from '../shared/lib/utils'

export type NextConfigComplete = Required<NextConfig> & {
images: Required<ImageConfigComplete>
Expand Down Expand Up @@ -157,6 +158,8 @@ export interface ExperimentalConfig {
serverComponentsExternalPackages?: string[]

fontLoaders?: { [fontLoader: string]: any }

webVitalsAttribution?: Array<typeof WEB_VITALS[number]>
}

export type ExportPathMap = {
Expand Down
3 changes: 2 additions & 1 deletion packages/next/shared/lib/utils.ts
Expand Up @@ -41,6 +41,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 type NextWebVitalsMetric = {
id: string
startTime: number
Expand All @@ -49,7 +50,7 @@ export type NextWebVitalsMetric = {
} & (
| {
label: 'web-vital'
name: 'FCP' | 'LCP' | 'CLS' | 'FID' | 'TTFB' | 'INP'
name: typeof WEB_VITALS[number]
}
| {
label: 'custom'
Expand Down
5 changes: 5 additions & 0 deletions test/integration/relay-analytics/next.config.js
@@ -0,0 +1,5 @@
module.exports = {
experimental: {
webVitalsAttribution: ['CLS', 'LCP'],
},
}
4 changes: 0 additions & 4 deletions test/integration/relay-analytics/pages/_app.js
Expand Up @@ -21,7 +21,3 @@ export function reportWebVitals(data) {
;(window.__metricsWithAttribution ??= []).push(data)
}
}

reportWebVitals.config = {
attributions: ['LCP'],
}

0 comments on commit cc51c1e

Please sign in to comment.