Real User Monitoring
Observability

Real User Monitoring

Track frontend performance, JavaScript errors, and user sessions with Real User Monitoring. Measure Core Web Vitals, replay sessions, and correlate frontend issues with backend traces.

Real User Monitoring captures what your users actually experience. Page load times, layout shifts, input delays, JavaScript errors, and full session recordings. One script tag on your site. JuhJuh collects the data, scores your Web Vitals, and shows you where users hit friction before they file a bug report.

No synthetic tests. No lab simulations. Real browsers on real networks.

How it works

graph LR
    A[Your Site] -->|script tag| B[RUM SDK]
    B -->|Web Vitals| C[JuhJuh API]
    B -->|JS Errors| C
    B -->|Session Events| C
    C --> D[Dashboard]
    D --> E[Vitals]
    D --> F[Sessions]
    D --> G[Errors]
    D --> H[Traces]
  1. You add a lightweight JavaScript snippet to your site
  2. The SDK collects Web Vitals, JavaScript errors, navigation events, and user interactions
  3. Data flows to JuhJuh through a token-authenticated API
  4. The RUM dashboard shows vitals scores, session recordings, error aggregations, and backend trace correlations

Two setup paths

RUM works with JuhJuh-managed deployments and external sites you host elsewhere.

JuhJuh deployments. Toggle RUM on from the dashboard. JuhJuh generates the snippet and links metrics to your deployment's observability data, including backend traces.

External sites. Create a standalone RUM config, set your allowed origins, and paste the snippet into your site's HTML. You get the same vitals, sessions, and error tracking without a JuhJuh deployment.

Enable RUM

For a JuhJuh deployment

  1. Go to Infrastructure and open your deployment
  2. Find the Real User Monitoring section
  3. Click Enable RUM
  4. Set your sample rate (1.0 = all sessions, 0.5 = half)
  5. Copy the generated snippet and add it to your site's <head>

For an external site

  1. Open the RUM page from your organization's sidebar
  2. Click Create Config
  3. Enter your site name or domain
  4. Set a sample rate
  5. Optionally add allowed origins (comma-separated domains that can send data)
  6. Copy the snippet

The snippet

Add this to every page you want to monitor, inside the <head> tag:

```html

```

The data-token attribute authenticates your site. Each config gets a unique token. You can rotate tokens at any time from the dashboard; the old token stops working immediately.

Optional snippet attributes

Attribute Default Description
data-token Required Your RUM authentication token
data-sample-rate 1.0 Override the server-side sample rate (0.0 to 1.0)
data-mask-selectors None CSS selectors to mask in recordings (e.g., .credit-card, #ssn)
data-record-inputs false Set to true to capture form input values

Web Vitals

RUM measures six Core Web Vitals on every page load. These are the same metrics Google uses for search ranking signals.

Metric Good Needs Work Poor What it measures
LCP Under 2.5s 2.5s to 4s Over 4s Time until the largest visible element (image, heading, video) finishes painting
INP Under 200ms 200ms to 500ms Over 500ms Worst-case delay between a user interaction and the next visual update
CLS Under 0.1 0.1 to 0.25 Over 0.25 How much the page layout shifts unexpectedly during loading
TTFB Under 600ms 600ms to 1.8s Over 1.8s Time from request start until the first byte arrives from your server
FCP Under 1.8s 1.8s to 3s Over 3s Time until the browser paints any content at all (text, image, SVG)
FID Under 100ms 100ms to 300ms Over 300ms Delay before the browser processes the first user input (replaced by INP in most browsers)

How scores work

JuhJuh rates each metric as good, needs improvement, or poor based on the thresholds above. The dashboard shows score distributions across all your sessions so you can see what percentage of users have a good experience versus a poor one.

Vitals per page

The Vitals tab breaks down scores by URL. Click any page to see:

  • 24-hour trend charts for LCP, INP, and CLS
  • Score distribution (percentage of good, needs work, and poor samples)
  • Segmentation by device type, browser, and connection speed
  • The 10 slowest sessions with links to replay them
  • Frontend errors that occurred on that page
  • Performance analysis with specific suggestions for improvement

Session recordings

Every monitored session captures a timeline of user activity. Navigation events, clicks, JavaScript errors, network requests, and rage clicks.

What gets recorded

Event type Description
Navigation Page loads, client-side route changes, back/forward navigation
Click User clicks with target element information
Rage click Three or more clicks on the same element within one second
Error JavaScript exceptions and unhandled promise rejections
Network XHR and fetch requests with method, URL, status code, and duration
Custom Events you send programmatically through the SDK

Browsing sessions

Open the Sessions tab for any RUM config. Filter by All sessions, With errors (sessions that hit JavaScript exceptions), or With rage clicks (sessions where users clicked the same element repeatedly out of frustration).

Each session row shows the session ID, visitor IP, country, browser, OS, duration, page count, and flags for errors and rage clicks.

Session replay

Click any session to open the replay view. The player shows:

  • A URL bar displaying the current page as the user navigated
  • A chronological event timeline with color-coded entries
  • Playback controls with speed adjustment (0.5x to 4x)
  • A scrubber for jumping to specific moments
  • Network requests with status codes (green for success, amber for redirects, red for errors)

Below the player, you get a full list of frontend errors in that session and correlated backend traces when distributed tracing is enabled.


Error tracking

RUM captures every JavaScript error and unhandled promise rejection across your monitored pages.

What gets captured

Each error includes:

  • Error type (TypeError, ReferenceError, SyntaxError, UnhandledRejection, and others)
  • Error message
  • Full stack trace
  • Page URL where the error occurred
  • Source file, line number, and column number
  • Trace ID for backend correlation (when distributed tracing is active)

Finding errors

The Errors tab provides search and filtering:

  • Search across error messages, page URLs, and source files
  • Filter by error type to isolate specific categories
  • Filter by time range (1 hour, 6 hours, 24 hours, 7 days, 30 days)
  • Sort by newest, oldest, or error type

Click any error to expand its stack trace. If the error has a trace ID, you can click through to the correlated backend trace to see what happened on the server side.


Distributed tracing

RUM connects frontend errors to backend performance through W3C Trace Context headers.

sequenceDiagram
    participant Browser
    participant SDK as RUM SDK
    participant Server as Your Server
    participant JuhJuh

    Browser->>SDK: User triggers XHR/fetch
    SDK->>Server: Request + traceparent header
    Server-->>SDK: Response
    SDK->>JuhJuh: Error + trace_id
    JuhJuh->>JuhJuh: Correlate frontend + backend

When the SDK detects a same-origin request, it injects a traceparent header following the W3C Trace Context standard. If your backend records traces with the same trace ID, JuhJuh links them together.

Start from a user's session, find a JavaScript error, and follow it straight to the backend operation that caused it.

Enabling distributed tracing

The SDK injects trace headers automatically on same-origin requests. No configuration needed on the RUM side. Your backend needs to:

  1. Read the traceparent header from incoming requests
  2. Record traces with the trace ID from that header
  3. Send traces to your observability system

JuhJuh deployments with observability enabled handle this automatically.


Privacy and sampling

Sample rate

The sample rate controls what percentage of sessions are monitored.

Value Effect
1.0 Monitor every session
0.5 Monitor 50% of sessions (randomly selected)
0.1 Monitor 10% of sessions
0.0 Monitoring paused, no data collected

Lower sample rates reduce data volume and API traffic. For high-traffic sites, a rate between 0.1 and 0.5 gives statistically meaningful data without overwhelming your storage.

Input masking

By default, RUM does not record form input values. If you enable input recording with data-record-inputs="true", use data-mask-selectors to exclude sensitive fields:

```html

```

Masked elements are hidden from session recordings and event metadata.

Allowed origins

Restrict which domains can send data with your token. This prevents unauthorized sites from using your RUM token if it is exposed.

  • Empty (default): accept data from any origin
  • Configured: only accept data from listed domains

Set allowed origins when creating or editing a RUM config. Separate multiple domains with commas.

Data retention

RUM data is retained for a configurable number of days per config. After the retention period, session recordings, vitals samples, and error records are permanently deleted.


Managing configs

The RUM dashboard

The main RUM page shows all your configs with 24-hour aggregate stats. Each row displays the average LCP, INP, and CLS with color-coded ratings, the session count, a status badge, and quick links to sessions, errors, and vitals.

Editing a config

Click the edit icon on any config row to update the name (standalone configs only), sample rate, or allowed origins. Changes take effect immediately for new sessions.

Rotating tokens

If a token is compromised, click Rotate Token on the config row. JuhJuh generates a new token and the old one stops working immediately. Update the snippet on your site with the new token.

Deleting a config

Deleting a config permanently removes all associated session recordings, vitals samples, and error records. This action cannot be undone.


SDK reference

The RUM SDK exposes a global window.__juhjuh_rum object for debugging:

Property Description
sessionId Current session identifier
traceId Current page's trace ID
config Active SDK configuration
vitals Latest collected Web Vitals values

Event flushing

The SDK batches events and flushes them periodically:

  • Events queue with a 5-second debounce
  • Automatic flush every 10 seconds
  • Immediate flush when the user navigates away or switches tabs
  • Vitals are sent separately, cached until a page change or 30-second timeout
  • Errors are sent immediately without batching

Network overhead

The SDK is small and stays out of your way:

  • Under 15 KB minified and compressed
  • Events are batched to reduce request count
  • Vitals use the browser's native PerformanceObserver API, no polling
  • The SDK excludes its own API calls from network instrumentation

API endpoints

RUM data is ingested through three endpoints. All require a valid token in the X-RUM-Token header.

Endpoint Method Purpose
/api/v1/rum/session/ POST Flush session events and optional vitals
/api/v1/rum/vitals/ POST Send Web Vitals independently
/api/v1/rum/error/ POST Report a JavaScript error immediately

Rate limiting

Each endpoint allows 200 requests per 60 seconds per token. Exceeding this limit returns a 429 status code.

Payload limits

  • Maximum 512 KB per request
  • Maximum 1,000 events per session flush

  • Observability for an overview of all observability signals
  • Tracing for backend distributed tracing that connects to frontend sessions
  • Instrumentation for connecting your backend to send traces
  • Alerts for setting up notifications on frontend performance thresholds
  • Deployments for deploying your application to JuhJuh-managed VMs
  • API for the full API reference including authentication and rate limits