Skip to content

Browser monitoring (RUM)

Browser monitoring (real user monitoring, or RUM) tells you what your users actually experience: page loads, Web Vitals, JavaScript errors, and the requests each page fires. Because those requests carry a trace header, a slow page in the browser links straight through to the backend trace it triggered — one timeline from the click to the database and back.

Optional session replay records the page as the user saw it, so you can watch exactly what happened around an error instead of guessing from a stack trace.

Browser monitoring is a small script you add to your web app. Point it at your ingest gateway and give the app a name:

import { init } from '@ultrvlt/browser-rum'
const rum = init({
endpoint: 'https://gateway.example.com',
app: 'storefront-web',
})
rum.setUser('user-123') // optional, once you know who they are

That’s enough to start capturing page views, Web Vitals, errors, and fetch timing. Everything ships to the same gateway your backend telemetry already uses — there’s no third-party service in the path, and browser data never leaves your infrastructure.

init({
endpoint: 'https://gateway.example.com',
app: 'storefront-web',
siteId: 'us-east', // scope this app to a site
sessionSampleRate: 0.5, // record half of sessions (defaults to all)
instrument: { // every signal is on by default
pageview: true,
fetch: true,
errors: true,
vitals: true,
},
})

Session replay is opt-in. Turn it on with replay: true, or pass an object to tune it:

init({
endpoint: 'https://gateway.example.com',
app: 'storefront-web',
replay: {
enabled: true,
sampleRate: 0.1, // record 1 in 10 sampled sessions
maskPreset: 'strict', // privacy preset (see below)
},
})

Replays are captured in the browser and streamed to your gateway in small chunks; the player in the portal reassembles them so you can scrub through a session alongside its telemetry.

Masking is on by default — nothing readable leaves the browser unless you say so. A masking preset picks sensible defaults, and you can override either flag:

Preset Inputs Visible text Use it when
strict masked masked Default. Nothing readable is recorded.
balanced masked recorded You want a readable layout but never form values.
off recorded recorded Only for apps with no sensitive data at all.
replay: {
enabled: true,
maskPreset: 'balanced',
maskAllText: true, // an explicit flag always wins over the preset
}

For finer control, mark individual elements with CSS classes: ultrvlt-block fully blocks an element, ultrvlt-mask masks its text, and ultrvlt-ignore drops its input events.

Browser telemetry arrives from untrusted browsers over the public internet, so the gateway gives you three controls for the browser-facing path. All apply only to browser requests — server-to-server telemetry over a verified connection is unaffected.

  • Allowlisted origins — list the web origins permitted to send browser telemetry. Anything else is refused. This is required for browser ingest to work at all.
  • Ingest key — a key the SDK presents on every upload. Set it in the gateway, then pass the same value as ingestKey in init(...). It’s a throttling credential to deter casual abuse of the public endpoint, not a strong secret (it ships in your page), but it lets you cut off a noisy or leaked key.
  • Per-origin rate limit — cap how fast any one origin can send, so a runaway page or a bad actor can’t flood ingest.
init({
endpoint: 'https://gateway.example.com',
app: 'storefront-web',
ingestKey: 'rum-pub-abc123', // matches the gateway's configured key
})

You configure the allowlist, key, and rate limit in the Helm chart under ingestGateway.rum — see the configuration reference.

Per-site keys and reliable site separation

Section titled “Per-site keys and reliable site separation”

If you run more than one site, give each one its own ingest key instead of a single shared one. Map each key to a site in the chart, and set the matching key in that app’s SDK:

// storefront in us-east
init({
endpoint: 'https://gateway.example.com',
app: 'storefront-web',
siteId: 'us-east',
ingestKey: 'rum-pub-east-abc123', // the key mapped to us-east in the chart
})
# Helm values
ingestGateway:
rum:
siteKeys:
- site: us-east
key: rum-pub-east-abc123
- site: eu-west
key: rum-pub-west-def456

When a browser presents a per-site key, the gateway tags the data with the site that key is mapped to, decided server-side — it does not trust the siteId the page claims. So a page can’t attribute its data to a site it isn’t keyed for, and when you set the site scope in the portal you see only that site’s browser sessions and recordings. This is the same trust model the product uses for connected clusters: identity is proven at ingest, not self-asserted.

A plain ingestKey (not mapped to a site) still works and authenticates uploads, but leaves the site as whatever the page reports — use per-site keys when you need data to be cleanly separated by site in the UI.

Recordings are kept for a fixed window (30 days by default) and then removed automatically, so replay storage doesn’t grow without bound. Adjust the window with blobStore.retentionDays in the chart; keep it at least as long as the window over which sessions stay queryable.