Skip to main content

React (web)

@base-14/scout-react ships zero-config Real User Monitoring for React web apps. One npm install, one Scout.initialize() call, and every click, route change, fetch request, error, Core Web Vital, long task, and lifecycle transition is captured as an OpenTelemetry span / metric / log and exported via OTLP to your Scout endpoint.

TL;DR

npm install @base-14/scout-react, call Scout.initialize({ serviceName, endpoint, headers }) once on app boot from the browser, optionally wrap your root with ScoutErrorBoundary. No manual Scout.track(...) calls needed — the SDK auto-instruments the entire browser-RUM surface.

Mobile (React Native)?

The same SDK package targets React Native. See the React Native + React Web instrumentation guide for the full reference including native crash capture, ANR detection, session-context persistence, and Expo integration.

What you get

CapabilitySignalHow it's captured
Route / page navigationscreen_view ROOT span with view.id, view.loading_type, view.referrer, screen_load span with screen.load_timeHistory API + popstate listener
Click trackinguser_interaction span (type=click, target selector, x/y, composed-path)Capture-phase click listener
Frustration signalsuser_interaction.action.frustration.type (rage_click / dead_click / error_click)DOM mutation observer + error correlation
Fetch / XHRhttp.request span with method / url.full / http.response.status_code / http.duration_ms / phase breakdown (DNS / connect / SSL / TTFB / download / redirect) / network.protocol.name / GraphQL operation parse / third-party provider classification (Stripe / CloudFront / Google Fonts / …)Global fetch + XMLHttpRequest wrap + PerformanceResourceTiming
Errorserror span with error.id, error.type, error.message, error.stack_trace, error.fingerprint, error.causes_json, breadcrumbswindow.onerror + unhandledrejection + ErrorBoundary
app_crash (catch-all)Emitted on next launch if the previous session didn't exit cleanly (pagehide never fired)Session marker in localStorage
Core Web Vitalsweb_vital spans for LCP / INP / CLS / FCP / TTFB plus sub-parts (input_delay, processing_duration, presentation_delay for INP; load_delay, load_time, render_delay for LCP; layout-shift rects for CLS)web-vitals library
Long taskslong_task span with long_task.duration, blocking_duration, render_start, style_and_layout_start, first_ui_event_timestamp, scripts_jsonPerformanceObserver('longtask') + long-animation-frame (Chrome 123+)
Frozen framesfrozen_frame span (≥ 700 ms blocks)Same PerformanceObserver
Scroll depthdisplay.scroll.max_depth, max_scroll_height, max_scroll_height_time_ms on screen_viewwindow.scroll listener with rAF coalescing
CSP violationserror span with error.csp.violated_directive, blocked_uri, dispositionsecuritypolicyviolation event listener
Page lifecycleapp_paused / app_resumed spans + view.page_states_json + view.in_foreground_periods_jsonvisibilitychange + freeze / resume events
Background flushAll batched signals force-flushed on visibilitychange=hidden / pagehideOTel BatchSpanProcessor.forceFlush()
Resource attributesservice.name, service.version, app.bundle_id, os.name, device.locale, network.connection.type, viewport.width/height, screen.pixel_ratio, a11y.* (~20 accessibility flags)Collected at init
Identity + session attrsuser.id + user.* (from setUser), account.id (from setAccount), feature_flag.* (from setFeatureFlag), arbitrary session bag (from setSessionAttributes)In-memory; merged into every span via commonAttributes()
Retry with jitterExponential backoff with full jitter on 5xx / 408 / 429 / network errors; configurable max retrieswrapWithRetry exporter wrapper
Offline bufferRetry-exhausted batches persisted to localStorage; replayed on Scout.initialize() + visibilitychange=visible + online eventsPer-signal FIFO item caps

Prerequisites

  • React 18 or 19
  • A Scout collector / RUM ingest endpoint reachable from the browser

Install

npm install @base-14/scout-react

Initialize

Scout.initialize() must run only in the browser. For pure CSR (Create React App, Vite without SSR), import it from your client entry. For any SSR setup (Next.js, Remix, Astro, Docusaurus, etc.), gate it with useEffect or a typeof window !== 'undefined' check so it doesn't run during SSR build time when window / document / localStorage are absent.

Pure CSR (Vite, CRA)

src/main.tsx
import Scout from '@base-14/scout-react';
import { ScoutErrorBoundary } from '@base-14/scout-react/react';
import { BrowserRouter } from 'react-router-dom';
import { createRoot } from 'react-dom/client';
import App from './App';

await Scout.initialize({
serviceName: 'my-web-app',
endpoint: 'https://rum.example.com/<tenant>/otlp',
secure: true,
headers: { Authorization: `Bearer ${import.meta.env.VITE_SCOUT_TOKEN}` },
});

createRoot(document.getElementById('root')!).render(
<ScoutErrorBoundary>
<BrowserRouter>
<App />
</BrowserRouter>
</ScoutErrorBoundary>,
);

SSR-aware (Next.js, Remix, Docusaurus, etc.)

Run Scout.initialize() inside useEffect (which only fires on the client), or guard a top-level module with a typeof window check:

components/ScoutBootstrap.tsx (Next.js client component)
'use client';
import { useEffect } from 'react';
import Scout from '@base-14/scout-react';

let initialized = false;

export function ScoutBootstrap() {
useEffect(() => {
if (initialized) return;
initialized = true;
void Scout.initialize({
serviceName: 'my-web-app',
endpoint: 'https://rum.example.com/<tenant>/otlp',
secure: true,
headers: { Authorization: `Bearer ${process.env.NEXT_PUBLIC_SCOUT_TOKEN}` },
});
}, []);
return null;
}

Render <ScoutBootstrap /> once at the root of your app.

The !initialized flag is a belt-and-braces idempotency check — bundler HMR sometimes re-evaluates top-level modules.

Identity + session attributes

Once Scout.initialize() resolves you can attach identity and arbitrary session-scoped attributes. Every subsequent span, metric, and log carries them automatically until you change or clear them.

// End-user identity → user.id + user.<key> on every span
Scout.setUser('user-123', {
email: 'jane@example.com',
plan: 'pro',
});

// B2B tenant → account.id + account.name
Scout.setAccount('acme-corp', 'Acme Corp');

// Session-scoped attribute bag (tenant id, build flavor, A/B cohort)
Scout.setSessionAttributes({
'tenant.id': 'acme',
'tenant.plan': 'enterprise',
'build.flavor': 'production',
});

// Feature flags — attached to every error span emitted while flags are active
Scout.setFeatureFlag('new-checkout', true);
Scout.setFeatureFlag('checkout-variant', 'B');

// Manual breadcrumbs (rolling 100-entry trail attached to every crash / error)
Scout.addBreadcrumb('checkout', 'added item to cart');

// Logs — go to the OTel log pipeline with active trace context
Scout.logInfo('app booted');
Scout.logError('payment failed', { 'order.id': 'ord-42' });

// Manual error report (handled errors)
try { /* … */ } catch (err) { Scout.reportError(err, { handled: true }); }

On sign-out, clear identity / account / flags:

async function signOut() {
await api.signOut();
Scout.clearUser();
Scout.clearAccount();
Scout.clearFeatureFlags();
Scout.clearSessionAttributes();
}

Filtering / PII redaction

Pass a beforeSend callback that runs on every span / metric / log before export. Return null to drop, or mutate the attributes object to redact:

Scout.initialize({
// …
beforeSend: (event) => {
if (String(event['url.full'] ?? '').includes('/health')) return null;
delete event['user.email'];
return event;
},
});

The callback sees per-span attributes only; resource attributes (e.g. service.name, os.name, device.*) aren't in the event payload.

CORS

The browser SDK exports via OTLP-HTTP. If your collector is on a different origin you'll need to allow CORS:

collector config
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
cors:
allowed_origins:
- "https://my-web-app.example.com"

Auto-instrumentation toggles

Every auto-instrumentation can be turned off independently — see the React Native + React Web reference for the complete list (enableErrorTracking, enableWebVitals, enableNetworkTracking, enableLongTaskDetection, enableAutoTapTracking, captureConsole, etc.). All default to true except captureConsole.

Sampling

sessionSampleRate defaults to 1 (1% of sessions) to bound telemetry volume in production. Error / crash / ANR / UI-hang spans bypass this gate (controlled by alwaysCaptureErrors, default true) so failures are always captured regardless of sampling. Below 100, full sessions are dropped (never partial) so traces stay coherent.

For development bump it to 100:

Scout.initialize({
// …
sessionSampleRate: 100,
});

Full reference

For the complete configuration surface (transport, batching, retry, offline buffer, thresholds, resource attrs, every enable* toggle, native crash setup, ANR detection, troubleshooting, FAQ), see the React Native + React Web instrumentation guide. The same package and the same APIs apply across React Native and React web — only the entry import (@base-14/scout-react vs @base-14/scout-react/native) and the runtime-specific captures differ.

What's next

References

Was this page helpful?