Frontend Observability: RUM and Core Web Vitals Monitoring
Healthy server metrics still can't answer "what does the user actually experience in the browser?". Real User Monitoring (RUM) collects real users' page loads, interactions, network requests, and errors from the browser side, turning "experience" into quantifiable, alertable data. As Datadog defines it, a user session contains page views, user actions, network resources, errors, and crashes, lasting up to 4 hours and expiring after 15 minutes of inactivity.
Why Core Web Vitals must be monitored
Per the official web.dev definition, Core Web Vitals are the three real-user metrics Google considers critical for every web page, with explicit thresholds:
| Metric | Dimension | Good threshold |
|---|---|---|
| LCP | Loading performance | ≤ 2.5 seconds |
| INP | Interaction responsiveness | ≤ 200 ms |
| CLS | Visual stability | ≤ 0.1 |
The guidance recommends evaluating at the 75th percentile, split by mobile and desktop. Lab tools (like Lighthouse) catch regressions before release, but only field data reflects the full picture of real devices, networks, and user behavior — which is exactly why RUM is irreplaceable.
Reference: https://web.dev/articles/vitals
Collecting and correlating with RUM
In practice, take three steps:
- Instrument: add a RUM SDK or use the
web-vitalslibrary, reporting LCP/INP/CLS together with context such as page, device, and region. - Analyze sessions: slice performance distribution by device, browser, region, and page to find common traits of "slow users". Datadog RUM lets you save custom searches as views and build monitors directly on them.
- Track errors and crashes: error tracking automatically groups and alerts on errors, timeouts, and crashes, sharply cutting MTTR; session replay lets you rewatch real user interactions like a video.
Integration example: web-vitals and a minimal RUM reporter
If you do not want to adopt a commercial SDK right away, start by collecting data yourself with the web-vitals library. Here is a minimal working setup:
npm install web-vitals
import { onLCP, onINP, onCLS } from 'web-vitals';
function report(metric) {
navigator.sendBeacon('/api/rum', JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
path: location.pathname,
ua: navigator.userAgent,
}));
}
onLCP(report);
onINP(report);
onCLS(report);
sendBeacon reliably delivers data during unload or when the tab goes to background, avoiding the lost requests you get from fetch right as the tab closes. metric.rating is the rating the library computes from the thresholds, so your backend can simply count poor. After wiring it up, split the SDK into a separate async chunk so it does not slow LCP.
From frontend metrics to alerts
RUM's value is turning experience metrics into alert sources — for example, "LCP P75 above 2.5s" or "JS error rate above threshold". Two points to remember: use percentiles and ratios rather than averages (averages hide a bad long tail), and correlate RUM alerts with backend traces — a slow page often traces back to API latency or CDN hit ratio.
Example alert thresholds
Here are common frontend performance alerts as a starting point (tune the exact thresholds per business):
| Alert | Suggested threshold | Notes |
|---|---|---|
| LCP P75 | > 2.5s | first paint slowing; check assets and images |
| INP P75 | > 200ms | interaction lag; check long tasks and main thread |
| CLS P75 | > 0.1 | layout shift; check image sizes and font loading |
| JS error rate | > 1% of sessions | pair with error tracking to find version regressions |
| Crash rate | > 0.1% of sessions | prioritize; often device/browser specific |
Alert fatigue is the enemy. Scope alerts to specific pages (for example, core landing pages only) and require the condition to persist for N minutes before firing, so jitter does not cause false alarms.
Trade-offs in collection: sampling, privacy, and overhead
Is "more RUM data" always better? Not exactly. First, sampling: for high-traffic sites, collecting every session costs bandwidth and adds overhead; a common approach is proportional sampling (e.g., 10%-20%) while keeping errors and crashes at 100% so critical signals are never missed. Second, privacy and compliance: real user data includes page paths, device info, and even input content; before rollout, confirm whether GDPR or similar rules apply and configure redaction so sensitive fields are never sent. Finally, performance budget: the RUM SDK itself consumes bandwidth and main-thread time — compress, load async, and initialize on demand to minimize its impact on LCP and INP, or the monitor itself becomes the performance regression.
FAQ
- Can RUM replace Lighthouse? No. Lighthouse is a synthetic test that reflects "performance on one fixed device" and suits pre-release regression; RUM reflects the real user distribution. One catches "will it break", the other "how slow is it actually".
- What sampling rate should I use? There is no universal answer. Low-traffic sites can sample 100%; high-traffic sites start at 10% and adjust after comparing sampled data with backend logs. Keep errors and crashes at 100%.
- How do I handle privacy? Disable or redact input-content collection, avoid personally identifiable fields, and set a data-retention period per compliance requirements. Better to collect less than to be non-compliant.
- What if I only get mobile data? Look at mobile and desktop separately, filtering by device in the RUM dashboard, and prioritize optimization by the device that dominates your traffic.
16IDC Take
For independent sites, RUM is a highly cost-effective "user health report": it both answers experience questions in the SEO context (see SEO performance tracking) and drives concrete optimization (see Core Web Vitals optimization). If you already run Sentry, extend it with RUM capabilities (Sentry error monitoring); synthetic monitoring handles the "dress rehearsal" while RUM handles the "post-mortem" — the complementary pair is covered in synthetic monitoring in practice. See more in the Monitoring & Alerting category.