Error Monitoring and RUM Design
Frontend system design for error monitoring and RUM — capture, sampling, privacy, source maps, and alerting.
- system-design
- interview
- architecture
- observability
- rum
Scope the problem
In scope: browser error capture, performance RUM (Core Web Vitals), event pipelines, privacy, sampling, dashboards/alerts for frontend teams.
Out of scope: full APM backend storage engine design.
Goals
| Goal | Example |
|---|---|
| Detect | spike in JS exceptions after deploy |
| Diagnose | stack + release + user path |
| Performance | LCP/INP/CLS by route |
| Privacy | no PII/secrets in payloads |
Architecture
Browser SDK
├─ window.onerror / unhandledrejection
├─ Framework error boundaries hook
├─ PerformanceObserver (vitals)
└─ transport (batch + beacon)
↓
Ingest API → process (symbolicate) → store → alert + UI
Error capture
window.addEventListener("error", (e) => {
report({
type: "error",
message: e.message,
stack: e.error?.stack,
release: process.env.RELEASE,
route: location.pathname,
});
});
window.addEventListener("unhandledrejection", (e) => {
report({ type: "unhandledrejection", reason: String(e.reason) });
});
React: error boundary componentDidCatch → report + fallback UI.
Grouping
Server fingerprints by:
- top stack frames (normalized)
- message
- release
Avoid grouping by full URL query strings (cardinality explosion).
Source maps
- Upload source maps at build for each
release - Never expose maps publicly if they reveal sensitive code (authenticated symbolication)
- Collapse framework frames for readability
RUM / Web Vitals
import { onLCP, onINP, onCLS } from "web-vitals";
onLCP((m) => sendVital("LCP", m));
onINP((m) => sendVital("INP", m));
onCLS((m) => sendVital("CLS", m));
Dimensions: route, device class, country, connection (effectiveType), app version.
Custom marks: login_to_interactive, checkout_step_2.
Transport & reliability
| Concern | Approach |
|---|---|
| Batching | queue N events or T ms |
| Unload | sendBeacon / fetch(keepalive) |
| Failures | drop oldest; never block UI |
| Ad blockers | first-party ingest domain proxy |
Sampling
- Errors: often 100% (or rate-limit per session)
- Traces/vitals: 1–20% depending on traffic
- Head sampling vs tail (keep slow sessions) if supported
Rate-limit noisy errors (network offline loops).
Privacy & security
Scrub before send:
- emails, tokens,
Authorizationheaders - form field values
- query params like
?token=
Consent mode: disable RUM until accepted where required. Document retention.
Client SDK design
init({
dsn: "...",
release: "web@1.2.3",
environment: "prod",
sampleRate: 0.1,
beforeSend(event) {
return scrub(event);
},
});
Integrate with feature flags/release to mark regressions.
Alerting
- Error rate > baseline + stddev for release
- New fingerprint in first hour of deploy
- LCP p75 regression on
/checkout
Page owners via route ownership map.
Performance of the SDK itself
- Tiny async load; don’t block LCP
- Avoid heavy stack parsing on main thread
- Prefer sampling over capturing huge DOM snapshots by default
Tradeoffs
- Full session replay vs privacy/cost
- First-party proxy vs SaaS default endpoint
- 100% errors vs ingest cost
- Verbose context vs PII risk
Interview close
SDK capture points → scrubbing/sampling → release + source maps → vitals by route → alerts on deploy regression. Mention beacon reliability and ad blockers.
Release health workflow
- Tag every deploy with
release+ git SHA - Compare error rate and CWV p75 for 1–2 hours post-deploy against previous release
- Auto-page on new fingerprint volume or LCP regression on money routes
- Attach replay/trace only for sampled sessions to control cost
What “good” looks like in an interview
Draw the SDK → ingest → symbolicate → alert loop, then spend time on scrubbing, sampling, and release correlation. Mention that console.error wrapping and network instrumentation are optional layers, not the core.
Framework-specific notes
- React: error boundaries don’t catch event-handler errors — still need global handlers
- Vue/Svelte: equivalent app-level hooks
- Route-aware tagging beats “one giant project bucket” for ownership
Cost control
Session replay, full request bodies, and 100% vitals will dominate invoices at scale. Default to: all fatal errors, sampled vitals, scrubbed breadcrumbs, no raw passwords/PII ever.
Related on this site
- Core Web Vitals
- Performance Budget System Design
- Security Interview Talking Points
- System Design Interview Framework