Real User Monitoring (RUM) is a performance monitoring technique that captures data from actual users as they interact with your website or application. Instead of simulated test runs from data centers, RUM measures performance as your real users experience it — on their actual devices, browsers, network connections, and locations. It's the difference between testing your car on a closed track and understanding how it actually performs in rush-hour traffic.
How RUM Works
RUM works by injecting a lightweight JavaScript snippet into your web pages. This snippet runs in the user's browser and collects performance data:
- User visits your site
- Browser downloads and executes your RUM script
- Browser APIs (Performance API, Navigation Timing, Resource Timing) report timing data
- RUM script collects and batches this data
- Data is sent to your RUM backend (usually via beacon API to minimize impact)
- Your dashboard aggregates data across all users
<!-- Basic RUM implementation -->
<script>
// Navigation Timing API - measures page load performance
window.addEventListener('load', function() {
const timing = performance.getEntriesByType('navigation')[0];
const metrics = {
dns: timing.domainLookupEnd - timing.domainLookupStart,
tcp: timing.connectEnd - timing.connectStart,
ttfb: timing.responseStart - timing.requestStart,
download: timing.responseEnd - timing.responseStart,
domProcessing: timing.domContentLoadedEventEnd - timing.responseEnd,
totalLoadTime: timing.loadEventEnd - timing.startTime,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now()
};
// Send to analytics backend
navigator.sendBeacon('/api/rum', JSON.stringify(metrics));
});
</script>
Core RUM Metrics
RUM captures several performance metrics that together tell the story of user experience:
| Metric | What It Measures | User Impact | |---|---|---| | Time to First Byte (TTFB) | Server response time | Perceived server speed | | First Contentful Paint (FCP) | First visible content | "Is it loading?" | | Largest Contentful Paint (LCP) | Main content loaded | "Is it usable?" | | Cumulative Layout Shift (CLS) | Visual stability | Frustrating if high | | Interaction to Next Paint (INP) | Responsiveness | "Does it respond?" | | Total Blocking Time (TBT) | JavaScript blocking | Input delay | | DOM Content Loaded | HTML parsing complete | Technical load stage | | Full Page Load | Everything loaded | Complete experience |
Core Web Vitals from Real Users
Core Web Vitals (LCP, CLS, INP) are measured by Chrome and reported to the Chrome User Experience Report (CrUX). Your RUM data should align with these:
// Measure Core Web Vitals in your RUM
import { onLCP, onCLS, onINP, onFCP, onTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
navigator.sendBeacon('/api/rum', JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // 'good', 'needs-improvement', 'poor'
delta: metric.delta,
navigationType: metric.navigationType,
url: window.location.href,
sessionId: getSessionId()
}));
}
onLCP(sendToAnalytics);
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
RUM Data Dimensions
The power of RUM is slicing performance data by dimensions to understand who is affected:
Geographic — Is performance bad for users in Germany but fine in the US? That points to CDN or regional infrastructure issues.
Device type — Are mobile users experiencing 3x slower load times than desktop? That's a mobile optimization opportunity.
Browser — Is Safari 20% slower than Chrome? Maybe you're using an API that Safari handles differently.
Network connection — 4G users having worse experiences than WiFi? This affects prioritization decisions.
Page/Route — Is the checkout page slower than the product page? Different pages have different optimization needs.
-- RUM data analysis query
SELECT
country,
device_type,
browser,
connection_type,
COUNT(*) as page_views,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY lcp_ms) as lcp_p50,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY lcp_ms) as lcp_p75,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY lcp_ms) as lcp_p95,
COUNT(CASE WHEN lcp_ms <= 2500 THEN 1 END) * 100.0 / COUNT(*) as lcp_good_pct
FROM rum_events
WHERE timestamp > NOW() - INTERVAL '7 days'
GROUP BY country, device_type, browser, connection_type
ORDER BY page_views DESC
LIMIT 50;
RUM vs Synthetic Monitoring
RUM and synthetic monitoring are complementary, not competitive:
| Aspect | Synthetic Monitoring | RUM | |---|---|---| | Data source | Simulated test runs | Real user sessions | | Coverage | Predefined test paths | All user interactions | | Consistency | Same conditions each run | Variable conditions | | Speed of alerting | Near-real-time | Slightly delayed (traffic needed) | | Low-traffic sites | Works fine | Less data available | | Geographic coverage | Your chosen locations | Where your users are | | Pre-launch testing | Excellent | Not applicable | | Privacy | No user data | User data handling required | | Cost | Fixed (check frequency) | Variable (data volume) |
Use both:
- Synthetic monitoring for availability, API health, and consistent performance baselines
- RUM for understanding actual user experience, identifying real-world performance issues, and validating that performance improvements actually help users
Implementing RUM: Options
Commercial RUM solutions — Google Analytics 4, New Relic, Datadog RUM, Dynatrace, Elastic RUM. Quick to implement, feature-rich, but can be expensive at scale.
Open-source RUM — Boomerang.js, web-vitals library, OpenTelemetry browser instrumentation. More control, but requires infrastructure to store and analyze data.
Simple custom implementation — For basic metrics, the built-in browser Performance API is free and requires no dependencies:
// Minimal RUM implementation
class MiniRUM {
constructor(endpoint) {
this.endpoint = endpoint;
this.sessionId = this.generateSessionId();
this.init();
}
init() {
// Page load metrics
window.addEventListener('load', () => this.collectPageMetrics());
// Long tasks (JavaScript blocking)
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) { // Long task threshold: 50ms
this.send({ type: 'long-task', duration: entry.duration });
}
}
});
observer.observe({ entryTypes: ['longtask'] });
}
}
collectPageMetrics() {
const nav = performance.getEntriesByType('navigation')[0];
if (!nav) return;
this.send({
type: 'navigation',
url: window.location.pathname,
ttfb: nav.responseStart - nav.requestStart,
domLoad: nav.domContentLoadedEventEnd - nav.startTime,
fullLoad: nav.loadEventEnd - nav.startTime,
sessionId: this.sessionId
});
}
send(data) {
navigator.sendBeacon(this.endpoint, JSON.stringify({
...data,
timestamp: Date.now(),
userAgent: navigator.userAgent,
connection: navigator.connection?.effectiveType || 'unknown'
}));
}
generateSessionId() {
return Math.random().toString(36).substring(2);
}
}
// Initialize
const rum = new MiniRUM('/api/metrics');
Privacy Considerations
RUM collects data about real users, so privacy handling matters:
What data to collect — Collect performance metrics, not personally identifiable information. You don't need usernames or email addresses to understand performance.
Sampling — For high-traffic sites, sample 10-20% of sessions rather than collecting every pageview. You'll have sufficient data without the storage costs.
Consent — In regions with GDPR/CCPA requirements, RUM data collection may require consent. Check your legal requirements.
Data retention — Performance data older than 90 days rarely provides actionable insights. Set aggressive retention limits.
IP anonymization — If storing IP addresses for geographic analysis, anonymize them (remove last octet).
// GDPR-aware RUM initialization
function initRUM() {
// Only collect if consent given or in exempt region
if (hasAnalyticsConsent() || isExemptRegion()) {
const rum = new MiniRUM('/api/metrics');
rum.setSampling(0.1); // 10% sampling rate
rum.anonymizeIPs(true);
rum.setRetention(90); // 90-day retention
}
}
Making RUM Data Actionable
RUM data is only valuable if it leads to improvements. Structure your analysis:
Weekly performance review:
- LCP, CLS, INP trends over 7 days
- Top 5 slowest pages by p75 LCP
- Performance by device type and region
- New issues introduced by recent deployments
Monthly deep dive:
- User cohorts by performance tier (fast/moderate/slow)
- Correlation between performance and business metrics
- Progress on active performance projects
After each major deployment:
- Before/after comparison of Core Web Vitals
- Validate improvements reached actual users, not just synthetic tests
Conclusion
Real User Monitoring fills the gap that synthetic monitoring can't reach: the actual experience of real users on real devices with real network connections. It surfaces geographic disparities, device-specific issues, and the impact of performance changes on your actual user base. Used alongside synthetic monitoring (which catches issues before users do), RUM gives you the complete picture of your service's performance. AzMonitor's synthetic monitoring establishes your performance baselines and catches regressions early; RUM validates that your optimizations are actually reaching your users in the wild.
3 monitors free forever · No credit card needed · Set up in 2 minutes
Start monitoring free →