An utility library for collecting user-centric performance metrics.
Features:
- Modular design based on ES modules.
- Small size (2.5kb gzip). It's usually smaller when you use a few features and Tree Shaking.
- Graceful support of latest browser APIs like Performance Paint Timing, Network Information, or Device Memory.
- Fully featured User Timing API v3 support.
npm install uxm@next
Collect user-centric metrics and send data to your API (1.5Kb):
import { collectMetrics, createApiReporter, getDeviceInfo } from 'uxm'
const report = createApiReporter('/api/collect', { initial: getDeviceInfo() })
collectMetrics(['fcp', 'lcp', 'fid', 'cls'], ({ metricType, value }) => {
report({ [metricType]: value })
})
At the end of the session (on visibilitychange
event), your API receives a POST request (using sendBeacon
) with data for core UX metrics and a device information, like:
{
"fcp": 1409,
"fid": 64,
"lcp": 2690,
"cls": 0.025,
"url": "https://example.com/",
"memory": 8,
"cpus": 2,
"connection": { "effectiveType": "4g", "rtt": 150, "downlink": 4.25 }
}
Explore examples for building a robust real-user monitoring (RUM) logic. Size of each example is controlled using size-limit.
Report FCP and FID to Google Analytics (0.7 KB)
Use Google Analytics as a free RUM service, and report user-centric performance metrics. Learn more about using Google Analytics for site speed monitoring.
import { collectFcp, collectFid } from 'uxm'
collectFcp(reportToGoogleAnalytics)
collectFid(reportToGoogleAnalytics)
function reportToGoogleAnalytics(metric) {
ga('send', 'event', {
eventCategory: 'Performance Metrics',
eventAction: 'track',
[metric.metricType]: metric.value,
})
}
Measure React view render performance (0.65 KB)
A react-hook example that measures rendering performance and creates a custom user-timing measure.
import { time, timeEndPaint } from 'uxm'
export function App() {
useTime('render:app')
return 'Hello from React'
}
function useTime(label) {
time(label) // render started
useEffect(() => timeEndPaint(label), []) // render ended, and the browser paint has been procceed.
}
Build a custom layout-shift metric for SPA (0.8 KB)
Layout Instability is a flexible API that allows building custom metrics on top — like, measuring cumulative layout shift per view, not the whole session.
import { observeEntries } from 'uxm'
import { observeHistory } from 'uxm/experimental'
/** @type {{ url: string, cls: number }[]} */
let views = []
let cls = 0
// cummulate `layout-shift` values, with an input
observeEntries('layout-shift', (layoutShiftEntries) => {
layoutShiftEntries.forEach((e) => {
if (!e.hadRecentInput) cls += e.value
})
})
// observe `history` changes,
// and reset `cls` when a route changes
observeHistory((e) => {
views.push({ url: e.prevUrl, cls })
cls = 0
})
Collect CrUX-like metrics (1.6Kb)
Chrome UX Report (CrUX) is a great way to see how real-world Chrome users experience the speed of your website. But for privacy reasons, CrUX aggregates data only per origin.
This script collects detailed crux-like analytics on the URL level.
import { getDeviceInfo, collectLoad, collectFcp, collectLcp, collectFid, collectCls, onVisibilityChange } from 'uxm'
// init `metrics` and get device information
const { connection, url } = getDeviceInfo()
const metrics = { url, effectiveConnectionType: connection.effectiveType }
// collect loading metrics
collectLoad(({ value: load, detail: { domContentLoaded, timeToFirstByte } }) => {
metrics.timeToFirstByte = timeToFirstByte
metrics.domContentLoaded = domContentLoaded
metrics.load = load
})
// collect user-centric metrics
collectFcp(({ value }) => (metrics.firstContentfulPaint = value))
collectLcp(({ value }) => (metrics.largestContentfulPaint = value))
collectFid(({ value }) => (metrics.firstInputDelay = value))
collectCls(({ value }) => (metrics.cumulativeLayoutShift = value))
// all metrics are collected on "visibilitychange" event
onVisibilityChange(() => {
console.log(metrics)
// {
// "url": "https://example.com/",
// "effectiveConnectionType": "4g",
// "timeToFirstByte": 1204,
// "domContentLoaded": 1698,
// "load": 2508
// "firstContentfulPaint": 1646,
// "largestContentfulPaint": 3420,
// "firstInputDelay": 12,
// "cumulativeLayoutShift": 0.12,
// }
}, 1)
Metrics are the core of uxm
(uxm
is a 3-letter acronym that stands for User eXperience Metrics).
It focuses on metrics, that captures a user experience, instead of measuring technical details, that are easy to manipulate. This metrics are more representetive for a user, and the final purpose of a good frontend is to create a delightful user experience.
Each metric follows the structure:
metricType
<[string]> - a metric acronym, ex:lcp
,fid
, orcls
.value
<number> - a numeric value of a metric, ex:1804
forlcp
,4
forfid
, or0.129
forcls
.detail
<object> - an extra detail specific for an each metric, likeelementSelector
forlcp
, eventname
forfid
, ortotalEntries
forcls
.
with an exception for collectLoad
(it does not have a 3-letters acronym, and considered a legacy.)
Use a per-metric function for more granular control of the callback behavior and saving a bundle size.
This metrics are only available in Chromium-based browsers (Chrome, Edge, Opera).
The best way to understand a metric is to read web.dev/metrics and check the source.
The method is a shortcut for calling collectFcp
, collectFid
, collectLcp
, and collectCls
.
import { collectMetrics } from 'uxm'
const report = createApiReporter('/api/collect')
// pass a metric 3-letter acronym
collectMetrics(['fcp', 'fid'], (metric) => {
report({ [metric.metricType]: metric.value })
})
// or a metric options using an object and `type`
collectMetrics([{ type: 'lcp', maxTimeout: 1000 }], (metric) => {
report({ lcp: metric.value })
})
callback
<function> a callback with FcpMetric:metricType
<"fcp"
>value
<number> a time when the user can see anything on the screen – a fast FCP helps reassure the user that something is happening.
Collect First Contentful Paint (FCP) using paint
entries.
callback
<function> a callback withFidMetric
:
import { collectFid } from 'uxm'
collectFid((metric) => {
console.log(metric)
// { metricType: "fid", value: 1, detail: { duration: 8, startTime: 2568.1, processingStart: 2568.99, processingEnd: 2569.02, name: "mousedown" }
})
callback
<function> a callback withLcpMetric
:metricType
<"lcp"
>value
<number> a time when the page's main content has likely loaded – a fast LCP helps reassure the user that the page is useful.detail
<object>elementSelector
<[string]> CSS selector of an element, that is triggered the most significant paintsize
<number> size (height
xwidth
) of the largest element
options
<object> (Optional)maxTimeout
<number> The longest delay betweenlargest-contentful-paint
entries to consider the LCP. Defaults to10000
ms.
Collect Largest Contentful Paint (LCP) using largest-contentful-paint
entries.
A callback triggers when a user interacts with a page, or after maxTimeout
between entries, or on "visibilitychange"
event.
import { collectLcp } from 'uxm'
collectLcp((metric) => {
console.log(metric) // { metricType: "lcp", value: 2450, detail: { size: 8620, elementSelector: "body > h1" } }
})
callback
<function> a callback withClsMetric
:
import { collectCls } from 'uxm'
collectCls(
(metric) => {
console.log(metric) // { metricType: "cls", value: 0.0893, detail: { totalEntries: 2, sessionDuration: 2417 } }
},
{ maxTimeout: 1000 }
)
callback
<function> a callback withClsMetric
:
import { collectLoad } from 'uxm'
collectLoad(({ value: load, detail: { domContentLoaded, timeToFirstByte } }) => {
console.log({ timeToFirstByte, domContentLoaded, load })
})
Made with ❤️ by Treo.