diff --git a/packages/sui-react-web-vitals/src/index.js b/packages/sui-react-web-vitals/src/index.js index 91edeed84..8f072ca10 100644 --- a/packages/sui-react-web-vitals/src/index.js +++ b/packages/sui-react-web-vitals/src/index.js @@ -1,19 +1,30 @@ import {useContext, useEffect, useRef} from 'react' import PropTypes from 'prop-types' -import * as reporter from 'web-vitals' +import * as reporter from 'web-vitals/attribution' import SUIContext from '@s-ui/react-context' import useMount from '@s-ui/react-hooks/lib/useMount/index.js' import {useRouter} from '@s-ui/react-router' export const METRICS = { - TTFB: 'TTFB', - LCP: 'LCP', CLS: 'CLS', + FCP: 'FCP', FID: 'FID', INP: 'INP', - FCP: 'FCP' + LCP: 'LCP', + TTFB: 'TTFB' +} + +const DEFAULT_METRICS_REPORTING_ALL_CHANGES = [METRICS.LCP, METRICS.INP] + +const DEFAULT_CWV_THRESHOLDS = { + [METRICS.CLS]: 100, + [METRICS.FCP]: 1800, + [METRICS.FID]: 100, + [METRICS.INP]: 200, + [METRICS.LCP]: 2500, + [METRICS.TTFB]: 800 } export const DEVICE_TYPES = { @@ -27,11 +38,13 @@ const getNormalizedPathname = pathname => { } export default function WebVitalsReporter({ - metrics = Object.values(METRICS), - pathnames, + children, deviceType, + metrics = Object.values(METRICS), + metricsAllChanges = DEFAULT_METRICS_REPORTING_ALL_CHANGES, onReport, - children + pathnames, + thresholds = DEFAULT_CWV_THRESHOLDS }) { const {logger, browser} = useContext(SUIContext) const router = useRouter() @@ -58,7 +71,24 @@ export default function WebVitalsReporter({ return deviceType || browser?.deviceType } - const handleReport = ({name, value}) => { + const handleAllChanges = ({attribution, name, value}) => { + const amount = name === METRICS.CLS ? value * 1000 : value + const pathname = getPathname() + const isExcluded = + !pathname || (Array.isArray(pathnames) && !pathnames.includes(pathname)) + + if (isExcluded || !logger?.log || amount < thresholds[name]) return + + logger.log( + JSON.stringify({ + name: `cwv.${name.toLowerCase()}`, + amount, + ...attribution + }) + ) + } + + const handleChange = ({name, value}) => { const onReport = onReportRef.current const pathname = getPathname() const routeid = getRouteid() @@ -66,9 +96,7 @@ export default function WebVitalsReporter({ const isExcluded = !pathname || (Array.isArray(pathnames) && !pathnames.includes(pathname)) - if (isExcluded) { - return - } + if (isExcluded) return if (onReport) { onReport({ @@ -81,9 +109,7 @@ export default function WebVitalsReporter({ return } - if (!logger?.distribution) { - return - } + if (!logger?.distribution) return const amount = name === METRICS.CLS ? value * 1000 : value @@ -120,7 +146,9 @@ export default function WebVitalsReporter({ } metrics.forEach(metric => { - reporter[`on${metric}`](handleReport) + reporter[`on${metric}`](handleChange) + if (DEFAULT_METRICS_REPORTING_ALL_CHANGES.includes(metric)) + reporter[`on${metric}`](handleAllChanges, {reportAllChanges: true}) }) }) @@ -129,23 +157,33 @@ export default function WebVitalsReporter({ WebVitalsReporter.propTypes = { /** - * An optional array of core web vitals. Choose between: TTFB, LCP, FID, CLS and INP. Defaults to all. + * An optional children node */ - metrics: PropTypes.arrayOf(PropTypes.oneOf(Object.values(METRICS))), + children: PropTypes.node, /** * An optional string to identify the device type. Choose between: desktop, tablet and mobile */ deviceType: PropTypes.oneOf(Object.values(DEVICE_TYPES)), /** - * An optional array of pathnames that you want to track + * An optional array of core web vitals. Choose between: TTFB, LCP, FID, CLS and INP. Defaults to all. */ - pathnames: PropTypes.arrayOf(PropTypes.string), + metrics: PropTypes.arrayOf(PropTypes.oneOf(Object.values(METRICS))), + /** + * An optional array of core web vitals that will report on all changes. Choose between: TTFB, LCP, FID, CLS and INP. Defaults to LCP and INP. + */ + metricsAllChanges: PropTypes.arrayOf(PropTypes.oneOf(Object.values(METRICS))), /** * An optional callback to be used to track core web vitals */ onReport: PropTypes.func, /** - * An optional children node + * An optional array of pathnames that you want to track + */ + pathnames: PropTypes.arrayOf(PropTypes.string), + /** + * An object with METRICS as keys and thresholds as values + * Thresholds by default are those above which Google considers the page as "needs improvement" + * Lower thresholds could be set for fine-tuning, higher thresholds could be set for less noise when reporting all changes */ - children: PropTypes.node + thresholds: PropTypes.object }