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]- You add a lightweight JavaScript snippet to your site
- The SDK collects Web Vitals, JavaScript errors, navigation events, and user interactions
- Data flows to JuhJuh through a token-authenticated API
- 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¶
- Go to Infrastructure and open your deployment
- Find the Real User Monitoring section
- Click Enable RUM
- Set your sample rate (1.0 = all sessions, 0.5 = half)
- Copy the generated snippet and add it to your site's
<head>
For an external site¶
- Open the RUM page from your organization's sidebar
- Click Create Config
- Enter your site name or domain
- Set a sample rate
- Optionally add allowed origins (comma-separated domains that can send data)
- 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 + backendWhen 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:
- Read the
traceparentheader from incoming requests - Record traces with the trace ID from that header
- 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
Related¶
- 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