Uxm
A modular library for collecting front-end performance metrics.
Install / Use
/learn @treosh/UxmREADME
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.
Usage
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.
<details> <summary>Report FCP and FID to Google Analytics (0.7 KB)</summary>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,
})
}
</details>
<details>
<summary>Measure React view render performance (0.65 KB)</summary>
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.
}
</details>
<details>
<summary>Build a custom layout-shift metric for SPA (0.8 KB)</summary>
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
})
</details>
<details>
<summary>Collect CrUX-like metrics (1.6Kb)</summary>
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)
</details>
API
Metrics
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:1804forlcp,4forfid, or0.129forcls.detail<[object]> - an extra detail specific for an each metric, likeelementSelectorforlcp, eventnameforfid, ortotalEntriesforcls.
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.
collectMetrics(metrics, callback)
metrics<[array]<[string]|[object]>>callback<[function]>
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 })
})
collectFcp(callback)
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.
collectFid(callback)
callback<[function]> a callback withFidMetric:metricType<"fid">value<[number]>detail<[object]>name<[string]>duration<[number]>startTime<[number]>processingStart<[number]>processingEnd<[number]>
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" }
})
collectLcp(callback, [options])
callback<[function]> a callback withLcpMetric:metricType<"lcp">value<[number]> a time when the page's main content has likel
