Real User Monitoring produces a lot of data. The challenge isn't collecting metrics — it's knowing which ones matter and how to interpret what you see. Not all RUM metrics have equal impact on user experience or business outcomes. Focusing on the right metrics, with the right targets, and understanding what drives them is what separates useful RUM from a dashboard nobody looks at.
The Metric Hierarchy
Think of RUM metrics in three tiers:
Business metrics — Conversion rate, session duration, bounce rate. These are what ultimately matter but are hard to directly optimize.
User experience metrics — Core Web Vitals (LCP, CLS, INP). These directly reflect how users perceive your site.
Technical metrics — TTFB, DOM loading, resource timing. These explain why the user experience metrics look the way they do.
Start with business metrics to establish that performance matters. Then focus on user experience metrics to guide improvement. Use technical metrics to diagnose root causes.
Core Web Vitals in Depth
Largest Contentful Paint (LCP)
LCP measures when the largest visible element finishes loading. This is usually your hero image, main heading, or primary content block.
Good: ≤ 2500ms Needs improvement: 2500ms - 4000ms Poor: > 4000ms
Common LCP elements and how to identify them:
// Find what element is LCP on your pages
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log({
element: entry.element, // The DOM element
size: entry.size, // Element size (px)
renderTime: entry.renderTime, // When it rendered
loadTime: entry.loadTime, // When it loaded
url: entry.url, // For image/video LCP
tagName: entry.element?.tagName // img, video, div, etc.
});
}
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
LCP breakdown by cause:
| LCP Element Type | % of Pages | Primary Fix |
|---|---|---|
| Image | 70% | Compress, use WebP, add fetchpriority="high" |
| Text block | 20% | Reduce TTFB, eliminate render-blocking resources |
| Video poster | 5% | Optimize poster image |
| SVG | 5% | Inline or preload |
Cumulative Layout Shift (CLS)
CLS measures how much the page layout shifts after initial render. A score of 0.1 or below is good.
// Track CLS sources
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) { // Exclude user-triggered shifts
for (const source of entry.sources) {
console.log({
element: source.node,
previousRect: source.previousRect,
currentRect: source.currentRect,
shift_score: entry.value
});
}
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });
The most common CLS causes:
- Images without explicit dimensions (
widthandheightattributes) - Ads, embeds, or iframes without reserved space
- Fonts causing FOUT (Flash of Unstyled Text)
- Dynamically injected content above existing content
Interaction to Next Paint (INP)
INP replaced FID (First Input Delay) as the Core Web Vital for responsiveness. It measures the worst interaction latency throughout the user's session.
Good: ≤ 200ms Needs improvement: 200ms - 500ms Poor: > 500ms
// Track all interactions for INP analysis
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 200) { // Track slow interactions
console.log({
name: entry.name, // 'click', 'keydown', etc.
duration: entry.duration, // Total interaction time
processingStart: entry.processingStart - entry.startTime, // Input delay
processingEnd: entry.processingDuration, // Processing time
renderDelay: entry.duration - entry.processingEnd, // Render delay
target: entry.target
});
}
}
});
observer.observe({ type: 'event', durationThreshold: 16, buffered: true });
Navigation Timing Metrics
Navigation timing breaks down the page load into discrete phases:
DNS lookup → TCP connect → TLS handshake → Request → Response → DOM processing → Load
| | | | | | |
dns tcp ssl ttfb download domLoad fullLoad
Understanding each phase:
| Phase | Typical Good | What Causes It to Slow | |---|---|---| | DNS lookup | < 5ms (cached) to 50ms | DNS propagation issues, slow resolver | | TCP connect | < 20ms | Server geography, network route | | TLS handshake | < 50ms | Certificate validation, protocol version | | TTFB | < 200ms | Server processing, database queries | | Download | < 100ms | Response size, bandwidth | | DOM processing | < 500ms | JavaScript execution, DOM size |
Resource Timing: Where Is Time Being Spent?
Resource timing shows how long each resource takes to load — images, scripts, stylesheets, fonts, API calls:
// Analyze resource loading performance
function analyzeResourceTiming() {
const resources = performance.getEntriesByType('resource');
// Group by resource type
const byType = {};
for (const resource of resources) {
const type = resource.initiatorType; // 'script', 'img', 'css', 'fetch', etc.
if (!byType[type]) {
byType[type] = {
count: 0,
totalSize: 0,
totalDuration: 0,
slowest: null
};
}
byType[type].count++;
byType[type].totalSize += resource.transferSize;
byType[type].totalDuration += resource.duration;
if (!byType[type].slowest || resource.duration > byType[type].slowest.duration) {
byType[type].slowest = {
url: resource.name,
duration: resource.duration,
size: resource.transferSize
};
}
}
return byType;
}
// Example output analysis:
// {
// "script": { count: 8, totalSize: 245000, slowest: { url: "/bundle.js", duration: 1200 } },
// "img": { count: 12, totalSize: 890000, slowest: { url: "/hero.jpg", duration: 800 } },
// "fetch": { count: 3, totalSize: 15000, slowest: { url: "/api/user", duration: 340 } }
// }
Session-Level Metrics
Beyond page-level metrics, track session-level performance:
Pages per session — Poor performance reduces pages viewed. A performance improvement that increases this metric directly increases engagement.
Session duration — Users with faster load times spend more time on site.
Rage clicks — Rapid clicks on an unresponsive element indicate poor INP or broken functionality.
Scroll depth — If users aren't scrolling, either content isn't relevant or the page is too slow to render what they came for.
// Track rage clicks (3+ clicks in 500ms)
class RageClickDetector {
constructor(threshold = 3, window = 500) {
this.threshold = threshold;
this.window = window;
this.clicks = [];
document.addEventListener('click', (e) => this.track(e));
}
track(event) {
const now = Date.now();
this.clicks = this.clicks.filter(t => now - t < this.window);
this.clicks.push(now);
if (this.clicks.length >= this.threshold) {
// Rage click detected
this.report({
element: event.target.tagName,
elementId: event.target.id,
elementClass: event.target.className,
clickCount: this.clicks.length,
url: window.location.href
});
this.clicks = [];
}
}
report(data) {
navigator.sendBeacon('/api/rum/rage-click', JSON.stringify(data));
}
}
Setting Performance Budgets with RUM Data
Use your RUM data to set realistic performance budgets:
// Performance budget based on actual user data
const performanceBudget = {
// Based on current p75 values from RUM
lcp: {
p75_target: 2500, // ms - "good" threshold
p90_target: 4000, // ms - acceptable degradation limit
alert_if_above: 4500 // ms - alert on regression
},
cls: {
p75_target: 0.1,
alert_if_above: 0.25
},
inp: {
p75_target: 200,
alert_if_above: 500
},
ttfb: {
p95_target: 600,
alert_if_above: 1000
}
};
// Check if new deployment regressed performance
function checkPerformanceBudget(currentMetrics, budget) {
const violations = [];
for (const [metric, thresholds] of Object.entries(budget)) {
const current = currentMetrics[metric];
if (current > thresholds.alert_if_above) {
violations.push({
metric,
current,
threshold: thresholds.alert_if_above,
severity: 'critical'
});
} else if (current > thresholds.p75_target) {
violations.push({
metric,
current,
threshold: thresholds.p75_target,
severity: 'warning'
});
}
}
return violations;
}
Geographic Performance Analysis
Performance often varies significantly by region. Analyze your RUM data geographically:
-- Geographic performance analysis
SELECT
country_code,
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.75) WITHIN GROUP (ORDER BY ttfb_ms) as ttfb_p75,
AVG(cls_score) as avg_cls,
COUNT(CASE WHEN lcp_ms <= 2500 THEN 1 END) * 100.0 / COUNT(*) as lcp_good_pct
FROM rum_page_views
WHERE timestamp > NOW() - INTERVAL '30 days'
AND page_views > 100 -- Exclude regions with insufficient data
GROUP BY country_code
HAVING COUNT(*) > 100
ORDER BY lcp_p75 DESC
LIMIT 20;
If TTFB is uniformly high for a region but LCP varies, CDN coverage is likely the issue. If both TTFB and LCP are high, the problem is server-side for that region.
Correlating Performance with Business Metrics
The ultimate purpose of RUM is understanding how performance affects your business:
-- Correlate LCP with conversion rate
SELECT
CASE
WHEN lcp_ms <= 2500 THEN 'Good (≤2.5s)'
WHEN lcp_ms <= 4000 THEN 'Needs Work (2.5-4s)'
ELSE 'Poor (>4s)'
END as lcp_category,
COUNT(*) as sessions,
SUM(CASE WHEN converted THEN 1 ELSE 0 END) as conversions,
SUM(CASE WHEN converted THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as conversion_rate
FROM user_sessions
JOIN rum_page_views USING (session_id)
WHERE page_type = 'checkout'
AND timestamp > NOW() - INTERVAL '30 days'
GROUP BY lcp_category
ORDER BY lcp_ms;
When you can show that sessions with LCP > 4s have a 30% lower conversion rate, performance work becomes easy to prioritize.
Conclusion
RUM metrics are most valuable when they're connected to user outcomes and business results. The Core Web Vitals — LCP, CLS, and INP — are your primary user experience indicators. Navigation timing and resource timing explain why they're at their current levels. Geographic and device breakdowns show you who's most affected. And correlations with conversion rates and session metrics make the business case for optimization. Use AzMonitor's synthetic monitoring to catch performance regressions immediately after deployments, and RUM to validate that your performance improvements actually reach users in the real world with all their diverse conditions.
3 monitors free forever · No credit card needed · Set up in 2 minutes
Start monitoring free →