@elastic/apm-rum
Browser Real User Monitoring agent that streams page-load performance, Web Vitals, traces, and JS errors to Elastic APM.
Repository Health
Technical Analysis
@elastic/apm-rum is the official Real User Monitoring (RUM) JavaScript agent for the Elastic Stack. It runs directly in the browser and instruments page loads, single-page application route changes, XHR/fetch requests, and user click interactions, then ships the resulting transactions and spans to an APM Server so teams can see exactly how real visitors experience their web application.
Under the hood it wraps the shared @elastic/apm-rum-core engine, which patches browser APIs (history, XHR, fetch, event listeners) to build distributed traces automatically, and layers in Web Vitals capture (FCP, LCP, INP, FID, TBT, CLS) alongside JavaScript error reporting with sourcemap support. Sibling framework packages (@elastic/apm-rum-react, @elastic/apm-rum-angular, @elastic/apm-rum-vue) build on the same core to attribute route- and component-level timing correctly for single-page-app frameworks.
What You Get
- Automatic page-load transaction capture with full navigation timing waterfalls (DNS lookup, TCP connect, TTFB, DOM parsing)
- Core Web Vitals instrumentation out of the box — FCP, LCP, INP, FID, TBT, and CLS
- Distributed tracing across outgoing XHR/fetch requests via automatic W3C trace-context propagation
- Automatic JavaScript error capture with stack traces, sourcemap support, and a custom error-reporting API
- A shared core package (@elastic/apm-rum-core) plus dedicated React, Angular, and Vue integrations for accurate SPA route timing
Common Use Cases
- Tracking real-world page-load and route-change performance for a production single-page application
- Correlating slow API calls observed in the browser with backend traces captured by server-side Elastic APM agents
- Alerting on JavaScript error spikes immediately after a frontend deploy
- Trending Core Web Vitals (LCP, INP, CLS) across releases for UX and SEO reporting
Under The Hood
Architecture
The repo is a Lerna-managed monorepo with a clear ownership split: packages/rum-core/src holds the actual engine, where common/service-factory.js wires together a ConfigService, LoggingService, TransactionService, and performance-monitoring service, and common/patching.js monkey-patches browser globals (XMLHttpRequest, fetch, history, addEventListener) exactly once via patchAll() so all instrumentation is passive from the host app’s perspective. bootstrap.js gates this behind isPlatformSupported() and sets a module-level enabled flag that every downstream service reads — a single point where instrumentation could be silently disabled if the gating logic changes. The vanilla packages/rum/src/index.js package is a thin façade that caches one ApmBase singleton on window.elasticApm and re-exports init/apm, while rum-react, rum-angular, and rum-vue depend on rum-core directly (not on rum) and hook into each framework’s router or component lifecycle to enhance the same shared transaction service rather than reimplementing it.
Tech Stack
The core is written in plain ES2015+ JavaScript and transpiled through Babel with separate BABEL_ENV targets for CommonJS (dist/lib), ES modules (dist/es), and a webpack-built UMD bundle for script-tag usage; TypeScript is used only for hand-authored .d.ts declaration files, checked via a standalone tsc --project test/types/tsconfig.json script rather than compiled source. Testing runs on Karma + Jasmine against real browsers, with WebdriverIO driving end-to-end suites; Lerna orchestrates the npm-workspace monorepo, GitHub Actions runs CI/release/microbenchmark pipelines, and published UMD bundles are held to explicit bundlesize budgets (around 19.6 kB minified).
Code Quality
Rum-core alone carries 48 .spec.js files (bootstrap, opentracing, error-logging, patching, service-factory, apm-server, and more), executed against real browser engines via Karma rather than a DOM shim, which gives it meaningfully more cross-browser confidence than a typical jsdom-only test suite. ESLint is configured at the repo root and enforced through Husky pre-commit hooks and lint-staged, with Commitlint requiring conventional-commit messages. Error handling favors explicit try/catch around user-supplied callbacks and boolean state flags over thrown exceptions, so a misbehaving host page is less likely to crash from agent code. Type safety is opt-in only — the shipped .d.ts files are hand-written and checked separately from the untyped JS runtime, so there is no compile-time type checking on the actual implementation.
What Makes It Unique
Rather than a novel algorithm, its distinguishing trait is disciplined adherence to draft web-platform standards: it implements Core Web Vitals (LCP, INP, CLS, TBT) directly against PerformanceObserver-based Timing APIs, ships a genuine OpenTracing-compatible tracer inside rum-core/src/opentracing, and solves the common “attribute a post-load fetch to the right SPA transaction” problem with an afterFrame scheduling primitive that waits for paint before closing a transaction — a detail many RUM agents only approximate.