A tiny (800 B) utility to simplify web vitals reporting.
The web-vitals is a small and powerful library to accurately measure Web Vitals. It has no opinion on how to report data from the browser to analytics. The web-vitals-reporter makes collecting Web Vitals as simple, as sending one POST request.
Features:
- Collect Web Vitals with one request per session.
- Gather useful device information (dimensions).
- Handle edge-cases like multiple CLS calls, round values, and
sendBeaconfallback. - Report custom front-end metrics.
- Tiny (800 B), functional, and modular.
Report Core Web Vitals to an API endpoint:
import { getLCP, getFID, getCLS } from 'web-vitals'
import { createApiReporter } from 'web-vitals-reporter'
// Create a report function that sends a POST request at the end of the session.
// An example body: { id: '1591874424275-9122658877754', duration: 8357, LCP: 1721, FID: 3, CLS: 0.0319 }
const sendToAnalytics = createApiReporter('/analytics')
getLCP(sendToAnalytics)
getFID(sendToAnalytics)
getCLS(sendToAnalytics)Report Web Vitals with an extended device information:
import { getFCP, getTTFB, getCLS, getFID, getLCP } from 'web-vitals'
import { createApiReporter, getDeviceInfo } from 'web-vitals-reporter'
// Init report callback and add information about a device.
// An example body: { id: '1591874402350-8969370227936', duration: 19185, url: 'https://treo.sh/',
// referrer: 'https://github.com/, userAgent: 'Mozilla/5.0 ...',
// cpus: 8, memory: 8, connection: {rtt: 100, downlink: 5, effectiveType: '4g'},
// TTFB: 253, FCP: 502, LCP: 1487, FID: 6, CLS: 1.5602 }
const sendToAnalytics = createApiReporter('/analytics', { initial: getDeviceInfo() })
getTTFB(sendToAnalytics)
getFCP(sendToAnalytics)
getLCP(sendToAnalytics)
getFID(sendToAnalytics)
getCLS(sendToAnalytics)Measure performance for Next.js application:
import { createApiReporter } from 'web-vitals-reporter'
// init reporter
const report = createApiReporter('/analytics')
// export `reportWebVitals` custom function
export function reportWebVitals(metric) {
if (metric.label === 'web-vitals') {
report(metric)
} else {
report({ name: metric.name, value: metric.value })
}
}
// or just, `report` supports custom metrics:
export { report as reportWebVitals }Create a report function, that accepts Web Vitals' Metric or any { name: string, value: number } object.
At the end of the session, it sends collected data to url using a POST request.
Use initial to add extra context to the result object.
By default web-vitals-reporter only adds id and session duration. It's possible to rewrite id with the initial object.
import { getFID } from 'web-vitals'
import { createApiReporter, getDeviceInfo } from 'web-vitals-reporter'
const report = createApiReporter('/analytics', {
initial: { id: 'custom-id', cpus: getDeviceInfo().cpus },
})
getFID(report)
// reported body:
{
id: 'custom-id',
cpus: 8,
FID: 24,
duration: 4560 // session duration
}By default web-vitals-reporter uses sendBeacon and fallbacks to XMLHttpRequest.
Use onSend to implement a custom request logic, like logging data in development, or adding extra headers with window.fetch.
import { createApiReporter } from 'web-vitals-reporter'
// detect Lighthouse using an `userAgent`
const isLighthouse = Boolean(navigator.userAgent.match('Chrome-Lighthouse'))
// exclude `localhost`
const isLocalhost = location.origin.includes('localhost')
// don't send results to API when a page tested with Lighthouse
const report = createApiReporter('/analytics', {
onSend:
isLighthouse || isLocalhost
? (url, result) => {
console.log(JSON.stringify(result, null, ' '))
}
: null,
})To see output in the console, set Preserve log option and refresh the page.
By default web-vitals-reporter only rounds metric.value for known Web Vitals (code).
Use mapMetric to implement a custom metric mapping. For example:
import { createApiReporter } from 'web-vitals-reporter'
const report = createApiReporter('/analytics', {
mapMetric: (metric) => {
switch (metric.name) {
// capture LCP element and its size
case 'LCP': {
const entry = metric.entries[metric.entries.length - 1] // use the last
return {
largestContentfulPaint: metric.value,
largestContentfulElement: getCssSelector(entry.element), // custom helper
largestContentfulElementSize: entry.size,
}
}
// capture cumulative/largest/total layout shift
case 'CLS': {
return {
cumulativeLayoutShift: metric.value,
largestLayoutShift: Math.max(...metric.entries.map((e) => e.value)),
totalLayoutShifts: metric.entries.length,
}
}
// report more information about first input
case 'FID': {
const entry = metric.entries[0]
return {
firstInputDelay: metric.value,
firstInputName: entry.name,
firstInputTime: entry.startTime,
}
}
// default name –> value mapping
default:
return { [metric.name]: metric.value }
}
},
})
getLCP(report)
getFID(report)
getCLS(report)It is a helper that returns device information (connection type, memory size, or the number of CPU cores). Use these data to add dimensions to your analytics.
import { getDeviceInfo } from 'web-vitals-reporter'
console.log(getDeviceInfo())
// printed in console:
{
"url": "https://treo.sh/",
"referrer": "https://github.com/",
"userAgent": "Mozilla/5.0 ...",
"cpus": 8,
"memory": 8,
"connection": { "rtt": 100, "downlink": 5, "effectiveType": "4g" }
}Return types:
{
// The page URL from `location.href`.
url?: string,
// The referrer value from `document.referrer`.
// It's useful to detect unique visits, without cookies or fingerprinting
// https://docs.simpleanalytics.com/uniques
referrer?: string,
// The value of `navigator.userAgent` for browser detection
userAgent?: string,
// An approximate amount of device memory in gigabytes:
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory
memory?: number,
// The number of CPU cores:
// https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency
cpus?: number,
// The network information:
// https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation
connection?: {
effectiveType: string,
rtt: number,
downlink: number,
},
}Sponsored by Treo.sh - Page speed monitoring made simple.