Elastic APM’s Real User Monitoring (RUM) lets you see how actual users experience your frontend application, not just how your tests or synthetic checks say it performs.

Let’s see it in action. Imagine a simple React app. First, you need the APM agent in your index.html or equivalent entry point.

<!DOCTYPE html>
<html>
<head>
  <title>My Awesome App</title>
  <script src="https://unpkg.com/@elastic/apm-rum"></script>
  <script>
    const apm = elasticApm.init({
      serviceName: 'my-react-app',
      serverUrl: 'http://localhost:8200', // Your APM Server URL
      environment: 'production',
      // Optional: logLevel: 'debug' for troubleshooting
    });
  </script>
</head>
<body>
  <div id="root"></div>
  <script src="bundle.js"></script>
</body>
</html>

When a user loads this page, the APM agent automatically starts capturing page load times, JavaScript errors, and AJAX requests. It bundles this data and sends it to your Elastic APM Server.

Here’s what that data looks like in Kibana’s APM UI:

  • Services View: You see my-react-app listed, with its overall performance metrics (average response time, error rate, throughput).
  • Traces View: Clicking into my-react-app shows individual requests. You can filter by URL, user, browser, or even specific transaction types (like page loads). Each trace is a breakdown of what happened: the initial HTML fetch, subsequent JavaScript, API calls, etc.
  • Errors View: This is crucial. You see JavaScript exceptions, stack traces, and the context around them (browser, OS, user ID if configured).

The core problem RUM solves is the disconnect between backend performance and frontend reality. A backend might be blazing fast, but if your JavaScript is blocking the main thread, your users are seeing a frozen page. RUM captures that user-perceived performance.

Internally, the RUM agent works by instrumenting browser APIs. It uses PerformanceObserver for metrics like navigation timing and resource loading. For AJAX requests (using fetch or XMLHttpRequest), it wraps these calls to record duration, status codes, and associated trace information. JavaScript errors are caught via window.onerror and window.onunhandledrejection.

The exact levers you control are primarily within the elasticApm.init() configuration:

  • serviceName: Identifies your application in APM.
  • serverUrl: Where the RUM agent sends data. This must be your APM Server URL.
  • environment: Differentiates deployment environments (e.g., production, staging).
  • transactionSampleRate: Controls how many transactions (page loads, AJAX requests) are sent. A value of 1.0 means 100%. Lowering this can save bandwidth and processing if you have extremely high traffic.
  • distributedTracingOrigins: Essential for tracing requests across services. If your frontend makes calls to backend services also monitored by APM, configuring this allows trace propagation.

What most people don’t realize is how deeply you can integrate RUM with other Elastic Stack features. For instance, you can create Kibana dashboards that combine RUM data (page load times, JS errors) with backend APM data (API latency, database queries) and logs, giving you a holistic view of a user’s entire journey, from their browser to your deepest microservices. This correlation is powerful for pinpointing the root cause of performance issues that span multiple layers.

The next step is to configure custom transaction naming and context propagation for richer insights.

Want structured learning?

Take the full Elastic-apm course →