Skip to content
Last9
Book demo

Getting Started with Discover Applications

Set up the RUM SDK in your application to start monitoring real user performance, errors, and behavior.

Discover Applications tracks how your users experience your web application by collecting Core Web Vitals, performance data, JavaScript errors, and user interactions directly from their browsers. The RUM SDK provides lightweight, non-blocking instrumentation that captures real-world application behavior without impacting performance.

Prerequisites

Before installing the RUM SDK, ensure you have:

  • Client Token: A Client — Web Browser token from Ingestion Tokens. This token authenticates your application’s data collection.
  • Applications Base URL: Your base URL from the Applications integration page. This endpoint receives your application’s monitoring data.

Supported Frameworks

The RUM SDK integrates with popular frontend stacks out of the box:

  • React
  • Angular
  • Vue
  • Next.js, with support for SSR

If you run into framework-specific issues or need guidance for another setup, reach out to the Last9 team and we’ll help you get started quickly.

Installation & Setup

  1. Add the RUM SDK script to your index.html file in the <head> section:

    <script src="https://cdn.last9.io/rum-sdk/builds/stable/v2/l9.umd.js"></script>
  2. Initialize RUM in your application’s main component (typically App.js or index.js):

    import { useEffect } from 'react';
    function App() {
    useEffect(() => {
    L9RUM.init({
    baseUrl: "https://your-base-url",
    headers: {
    clientToken: "your-client-token",
    },
    resourceAttributes: {
    serviceName: "your-app-name",
    deploymentEnvironment: "production",
    appVersion: "1.0.0",
    },
    });
    }, []);
    return (
    // Your app components
    );
    }
  3. Add user tracking (optional):

    L9RUM.identify({ id, email, name, fullName, roles }) // on login
    L9RUM.clearUser() // on logout

Configuration

Required Configuration

These settings are mandatory for the RUM SDK to function:

L9RUM.init({
baseUrl: "https://your-base-url", // Your Applications base URL
headers: {
clientToken: "your-client-token", // Client authentication token
},
resourceAttributes: {
serviceName: "your-app-name", // Application identifier
deploymentEnvironment: "production", // Environment (production, staging, development)
appVersion: "1.0.0", // Version identifier (semver, git hash, etc.)
},
});

Optional Configuration

Customize the RUM SDK behavior with additional settings:

L9RUM.init({
// Required settings...
// Optional settings
sampleRate: 40, // Percentage of sessions to monitor (1-100, default: 40)
debug: false, // Enable console logging for troubleshooting (default: false)
// Control error tracking
errors: {
console: true, // Console errors (default: true)
global: true, // Unhandled exceptions (default: true)
report: true, // Browser reporting API (default: true)
network: true, // Failed requests (default: true)
ignorePatterns: [/ResizeObserver/i], // Regex patterns to ignore
beforeSend: (event) => {
// Filter or enrich errors before sending
if (event.attributes["exception.message"]?.includes("ExpectedNoise")) {
return null; // Drop this error
}
return event;
},
},
// Network tracking & backend trace correlation
network: {
// Control browser-side network tracking
enabled: true, // default: true; set false to disable all network spans
ignorePatterns: {
// Optionally ignore noisy endpoints
// Matches against the full URL (includes origin + path + query)
fullUrl: ["https://cdn.example.com", /^https:\/\/.*\\.example\\.com/],
// Matches against just the path portion (e.g., /healthz, /metrics)
pathname: ["/healthz", /^\\/internal\\/metrics/],
// Matches against just the hostname (e.g., api.example.com)
hostname: ["localhost", "api-internal.example.com"],
},
// Backend trace correlation
backendCorrelation: {
enabled: true,
corsAllowedOrigins: ["https://api.internal.example"],
customHeaders: { "x-app-name": "web-client" },
injectToAllRequests: false,
// W3C Baggage propagation (propagate custom attributes to backend)
baggage: {
enabled: true,
allowedKeys: ["user.id", "tenant.id"], // only these keys are propagated
},
},
},
// Capture user interactions
interactions: {
enabled: true, // opt in explicitly
trackClicks: true,
trackScroll: true, // throttled; default 500ms
trackKeyboard: false, // off by default for privacy
trackForm: true, // focus/input on non-sensitive fields
trackTouch: false,
captureElementText: false, // trim to 100 chars when enabled
selectorDepth: 3, // depth used for simple selectors
throttleMs: 500,
},
// Geo-IP enrichment (optional, privacy-sensitive)
geoIpEnabled: false, // default: false; set true to resolve IP + coarse geo via GeoJS
});

Network Tracking Controls

The SDK records browser network activity (fetch, XHR, and static resources) as spans attached to the active View. Use the network configuration to tune what is collected:

  • network.enabled: Master toggle for browser-side network tracking.
    • Default: true.
    • Set network: { enabled: false } to completely disable network spans (fetch, XHR, and resource loads) while keeping the rest of the SDK active.
  • network.ignorePatterns: Filter out noisy or internal endpoints to keep dashboards focused and reduce cardinality.
    • fullUrl: Matches against the complete URL (e.g., https://cdn.example.com/assets/app.js).
    • pathname: Matches against just the path (e.g., /healthz, /metrics).
    • hostname: Matches against the hostname (e.g., api.internal.example).
    • Each array accepts strings (substring match) or regular expressions.

By default, the SDK automatically ignores requests to localhost and 127.0.0.1 (including their IPv6 variants) so that local development noise does not pollute production-like data. You can still opt in to local traffic by sending data to a non-localhost hostname (for example, a tunneled URL or a staging endpoint).

Sample Rate Guidelines

Choose your sample rate based on traffic volume and environment:

Traffic LevelDaily UsersRecommended RatePurpose
High Traffic>10,0001-10%Manage data volume while maintaining statistical significance
Medium Traffic1,000-10,00010-25%Balance coverage with data volume
Low Traffic<1,00025-50%Maximize insights with comprehensive data collection
Development/StagingAny50-100%Thorough testing and validation before production

User Interaction Tracking

Instrument real user actions as spans, attached to the active View:

  • enabled: Master toggle (default: false)
  • trackClicks, trackScroll, trackKeyboard, trackForm, trackTouch: Turn on specific interaction types (all default to false)
  • throttleMs: Minimum gap between scroll spans (default: 500ms)
  • captureElementText: Include element text up to elementTextMaxLength (default: false, 100 chars)
  • selectorDepth: Depth for building simple CSS selectors (default: 3)

Privacy defaults: keyboard tracking is off unless you opt in, sensitive form fields (password/credit-card/email, etc.) are skipped automatically, and captured element text is trimmed to the configured length.

Backend Trace Correlation

Connect frontend requests to backend services for end-to-end visibility. When enabled, the SDK adds W3C trace headers (traceparent/tracestate) to outbound requests, allowing you to see complete request flows from browser to backend.

network: {
backendCorrelation: {
enabled: true,
corsAllowedOrigins: ["https://api.internal.example"], // Domains to include
customHeaders: { "x-app-name": "web-client" }, // Additional headers
injectToAllRequests: false, // Set true only if all APIs support it
},
}

Start with same-origin APIs, then progressively add cross-origin domains after verifying they accept the headers. Enable debug: true to identify configuration issues.

W3C Baggage Propagation

Baggage lets you forward custom key-value pairs from the browser to your backend services via the standard W3C Baggage HTTP header. This is useful when your backend needs context that originates in the browser, for example linking a session ID from another tool, or passing a tenant identifier for multi-tenant routing.

L9RUM.init({
// ...required settings
network: {
backendCorrelation: {
enabled: true,
corsAllowedOrigins: ["https://api.myapp.com"],
baggage: {
enabled: true,
allowedKeys: ["user.id", "tenant.id"],
},
},
},
});
// Set the attributes you want propagated
L9RUM.spanAttributes({
"user.id": currentUser.id,
"tenant.id": currentUser.tenantId,
});

The SDK sends these as a baggage header alongside the existing traceparent / tracestate headers:

baggage: user.id=user-456,tenant.id=tenant-xyz

Baggage values travel as plaintext HTTP headers. Only include non-sensitive attributes in allowedKeys. Never add secrets, tokens, passwords, or PII such as emails or phone numbers.

Baggage Configuration Reference

OptionTypeDefaultDescription
enabledbooleanfalseEnable W3C Baggage propagation
allowedKeysstring[][]Whitelist of attribute keys to propagate. Keys not listed here are never sent, even if set via spanAttributes()
trackedUrls(string | RegExp)[]Restrict baggage to specific URLs. If omitted, baggage follows the same rules as trace context (corsAllowedOrigins / injectToAllRequests)
maxTotalBytesnumber8192Maximum baggage header size in bytes (W3C recommendation). Entries are truncated if exceeded
warnAtPercentagenumber80Log a warning when baggage size exceeds this percentage of maxTotalBytes

Linking Sessions from Another Tool

A common use case is propagating a session ID from another tool:

L9RUM.init({
// ...required settings
network: {
backendCorrelation: {
enabled: true,
forceOverwrite: true, // required when running alongside another RUM SDK
baggage: {
enabled: true,
allowedKeys: ["external.session_id"],
},
},
},
});
// Capture the session ID from the other tool
L9RUM.spanAttributes({
"external.session_id": otherRum.getSessionId(),
});

Extracting Baggage on the Backend

Your backend services can read the baggage header using any OpenTelemetry SDK. Below are examples for common languages.

Register a BaggageSpanProcessor so every span automatically picks up baggage entries as attributes:

const api = require("@opentelemetry/api");
// Copies all W3C Baggage entries onto every span as attributes
class BaggageSpanProcessor {
onStart(span, parentContext) {
const baggage = api.propagation.getBaggage(
parentContext || api.context.active()
);
if (!baggage) return;
for (const [key, entry] of baggage.getAllEntries()) {
span.setAttribute(key, entry.value);
}
}
onEnd() {}
forceFlush() { return Promise.resolve(); }
shutdown() { return Promise.resolve(); }
}
// Add to your tracer provider setup
const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
const provider = new NodeTracerProvider();
provider.addSpanProcessor(new BaggageSpanProcessor());
provider.register();

Once registered, attributes like user.id and tenant.id appear on every backend span without per-route middleware.

Adding Custom Context

Global Attributes

Add business context to all monitoring data. Call L9RUM.spanAttributes() whenever context changes:

L9RUM.spanAttributes({
"app.org_slug": "acme-corp",
"feature.flag.beta_checkout": true,
});
// Clear attributes (e.g., on logout)
L9RUM.spanAttributes(null);

Note: Each call replaces all previous attributes, so always provide the complete set. These attributes become available as filters in the dashboard.

Custom Events

Track user actions and business events beyond standard page views:

L9RUM.addEvent("checkout_completed", {
plan: "pro",
amount: 99,
});

Events inherit session and view context automatically, making them easy to correlate with performance metrics and errors.

Manual Error Capture

Explicitly track handled exceptions to maintain visibility into recoverable failures:

try {
await searchProducts(query);
} catch (error) {
L9RUM.captureError(error, {
handled: true,
message: "search_query_timeout",
query,
page: 3,
});
}

Manual captures default to handled: true. All errors — automatic and manual, pass through the errors.beforeSend hook for filtering.

Data Collection

The RUM SDK automatically collects performance and context data without affecting your application’s performance.

Core Web Vitals

  • Largest Contentful Paint (LCP): Loading performance measurement for largest visible content element
  • First Contentful Paint (FCP): Initial rendering time for first visible content
  • Cumulative Layout Shift (CLS): Visual stability score measuring unexpected layout shifts
  • Interaction to Next Paint (INP): Responsiveness metric for user interactions
  • Time to First Byte (TTFB): Server response time from initial request

User Context

  • Browser Information: Name, version, and complete user agent string
  • Device Details: Type (desktop, mobile, tablet) and screen dimensions
  • Network Conditions: Connection type (4g, WiFi, etc.) and speed indicators
  • Geolocation: Country, region, city, IP, etc (only when geoIpEnabled: true and GeoJS enrichment succeeds)
  • User Identity: Name, email, role, and ID when provided via identify()

Page Information

  • Navigation Data: Page URL, path, referrer, and query parameters
  • Route Changes: Single-page application navigation and route transitions
  • Timing Metrics: Load times, navigation timing, and resource loading performance
  • Network Activity: API calls, resource loading, and network errors

Verification

Confirm the RUM SDK is working correctly:

  1. Check Browser Console: Look for RUM initialization messages. Enable debug: true in your configuration to see detailed logging.
  2. Verify Network Requests: Open browser DevTools > Network tab and look for requests to your Applications base URL. Successful requests indicate data is being sent.
  3. Inspect API Calls: Confirm trace headers appear on backend requests if correlation is enabled.
  4. View Applications Dashboard: Navigate to Discover > Applications and confirm data appears within 2-3 minutes of page loads.

Next Steps

With Applications monitoring successfully installed, explore the monitoring capabilities:


Troubleshooting

  • No data showing? Verify your baseUrl and clientToken are correct. Check the browser console for initialization errors.
  • Trace headers missing? Confirm network.backendCorrelation.enabled is true and target domains are in corsAllowedOrigins.
  • Baggage not arriving on backend? Verify baggage.enabled is true, the attribute keys are listed in allowedKeys, and the values are set via L9RUM.spanAttributes() before the request fires. Open DevTools → Network and check that the baggage header appears on outgoing requests.
  • Errors not appearing? Check that error toggles in the errors configuration are enabled and sampleRate is appropriate for your traffic.

Please get in touch with us on Discord or Email if you have any questions.