Watchlog Real User Monitoring (RUM) for React + React Router v6
npm install watchlog-react-rumš A comprehensive, production-ready Real User Monitoring (RUM) SDK for React apps ā powered by Watchlog.
Automatically track SPA route changes, performance metrics, user interactions, network requests, errors, and much more with zero configuration. Built to match the capabilities of DataDog and Sentry RUM.
---
/users/123 into /users/:idpage_view on every React Router navigationsession_start, page_view, session_end, custom, error, performance, network, resource, longtask, web_vital, interactionwindow.onerror, unhandledrejection, and React component errors---
``bash`
npm install watchlog-react-rum
Optional: For Web Vitals support (CLS, LCP, INP, TTFB, FID):
`bash`
npm install web-vitals
---
For the most accurate route pattern detection (like Datadog), use the wrapped router functions from watchlog-react-rum/react-router-v6:
`jsx
// src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import { createBrowserRouter } from 'watchlog-react-rum/react-router-v6'
import { useWatchlogRUM } from 'watchlog-react-rum'
import App from './App'
import Home from './pages/Home'
import UserDetail from './pages/UserDetail'
import PostDetail from './pages/PostDetail'
// Create router with route definitions (for accurate route pattern extraction)
// IMPORTANT: Use createBrowserRouter from 'watchlog-react-rum/react-router-v6'
// This ensures route patterns are captured for accurate normalization
const router = createBrowserRouter([
{
path: '/',
element:
children: [
{
index: true,
element:
},
{
path: 'users/:userId', // This exact pattern will be used for normalization
element:
},
{
path: 'posts/:postId',
element:
},
],
},
])
// Root component to initialize RUM tracking
function Root() {
useWatchlogRUM({
apiKey: 'YOUR_API_KEY',
endpoint: 'https://your-endpoint.com/rum',
app: 'your-app-name',
environment: 'production', // optional
release: '1.0.0', // optional
debug: false,
flushInterval: 10000, // ms
sampleRate: 0.5, // 0.0 to 1.0 - session sampling (max: 0.5 to prevent server overload)
networkSampleRate: 0.1, // 0.0 to 1.0 - network request sampling (recommended: 0.1)
interactionSampleRate: 0.1, // 0.0 to 1.0 - user interaction sampling (recommended: 0.1)
enableWebVitals: true,
captureLongTasks: true,
captureFetch: true,
captureXHR: true,
captureUserInteractions: false, // Set to true to enable click/scroll tracking
captureBreadcrumbs: true,
maxBreadcrumbs: 100,
beforeSend: (event) => {
// Optional: filter or modify events before sending
// Return null to drop the event
return event
}
})
return
}
ReactDOM.createRoot(document.getElementById('root')).render(
)
`
Important: Using the wrapped createBrowserRouter from watchlog-react-rum/react-router-v6 ensures that route patterns (like /users/:userId or /users/:uuid) are extracted directly from your route definitions, providing the most accurate normalization.
If you're using BrowserRouter instead of RouterProvider, the SDK will still work but will use params-based route reconstruction:
`jsx
// src/App.jsx
import { BrowserRouter } from 'react-router-dom'
import { useWatchlogRUM } from 'watchlog-react-rum'
function App() {
useWatchlogRUM({
apiKey: 'YOUR_API_KEY',
endpoint: 'https://your-endpoint.com/rum',
app: 'your-app-name',
// ... other config options
})
return (
{/ Your app routes /}
)
}
export default App
`
This automatically sends:
1. session_start on first load (with normalized path and referrer)page_view
2. on every route changesession_end
3. on unloaderror
4. for uncaught JS errors, unhandled promise rejections, and React component errorsperformance
5. metrics on each page loadweb_vital
6. metrics (CLS, LCP, INP, TTFB, FID, FCP, FP)network
7. requests (fetch/XHR) with detailed timingresource
8. loads (images, scripts, stylesheets, etc.)longtask
9. events when JavaScript blocks the main threadinteraction
10. events (if enabled)
---
`js
import WatchlogRUM from 'watchlog-react-rum'
// Initialize once at app startup
WatchlogRUM.init({
apiKey: 'YOUR_API_KEY',
endpoint: 'https://your-endpoint.com/rum',
app: 'your-app-name',
debug: true,
flushInterval: 10000,
})
// Send custom metric
WatchlogRUM.custom('button_clicked', 1, { extra: 'data' })
// Manually capture errors
WatchlogRUM.captureError(new Error('Something went wrong'), {
component: 'MyComponent',
props: { userId: 123 }
})
// Add breadcrumbs
WatchlogRUM.addBreadcrumb('user', 'User clicked button', 'info', {
buttonId: 'submit'
})
// Flush buffered events (e.g. before manual unload)
WatchlogRUM.flush(true)
`
Note: When using manual API, you need to manually track route changes:
`jsx
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import WatchlogRUM from 'watchlog-react-rum'
function MyComponent() {
const location = useLocation()
useEffect(() => {
// Manually track page views
WatchlogRUM.bufferEvent({
type: 'page_view',
path: location.pathname,
normalizedPath: location.pathname, // or compute normalized path
navType: 'navigate'
})
}, [location.pathname])
return
---
ā ļø Sample Rate Limits & Best Practices
$3
To protect server resources and prevent overload, the maximum allowed sampleRate is 0.5 (50%). If you set a value higher than 0.5, it will be automatically capped to 0.5.Recommended values:
- Development/Testing:
0.5 (50%) - Full visibility for debugging
- Production (Low Traffic): 0.3 (30%) - Good balance between data and performance
- Production (High Traffic): 0.1 (10%) - Efficient data collection without server strainWhy limit sample rate?
High sample rates can generate massive amounts of data, leading to:
- Server overload and potential crashes
- Increased storage costs
- Slower query performance
- Network bandwidth issues
$3
Network requests can be very frequent. We recommend keeping this at 0.1 (10%) or lower for production environments.$3
User interactions (clicks, scrolls) can be extremely frequent. We recommend 0.1 (10%) or lower for production.---
š¦ Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
apiKey | string | required | Your Watchlog API key |
| endpoint | string | required | RUM endpoint URL |
| app | string | required | Application name |
| environment | string | 'prod' | Environment (e.g., 'production', 'staging') |
| release | string | null | Release version (e.g., '1.0.0') |
| debug | boolean | false | Enable debug logging |
| flushInterval | number | 10000 | Flush interval in milliseconds |
| sampleRate | number | 1.0 | Session sampling rate (0.0 to 1.0). Note: Maximum allowed value is 0.5 (50%) to prevent server overload. Values above 0.5 will be automatically capped. |
| networkSampleRate | number | 0.1 | Network request sampling rate (0.0 to 1.0). Recommended: 0.1 (10%) for production. |
| interactionSampleRate | number | 0.1 | User interaction sampling rate (0.0 to 1.0). Recommended: 0.1 (10%) for production. |
| enableWebVitals | boolean | true | Enable Web Vitals tracking (requires web-vitals package) |
| autoTrackInitialView | boolean | true | Automatically track initial page view |
| captureLongTasks | boolean | true | Capture long tasks (>50ms) |
| captureFetch | boolean | true | Capture fetch requests |
| captureXHR | boolean | true | Capture XMLHttpRequest |
| captureUserInteractions | boolean | false | Capture user interactions (clicks, scrolls, forms) |
| captureBreadcrumbs | boolean | true | Capture event breadcrumbs |
| maxBreadcrumbs | number | 100 | Maximum number of breadcrumbs to keep |
| beforeSend | function | (ev) => ev | Filter/modify events before sending (return null to drop) |---
š¦ Exports
| Module | Description |
|--------|-------------|
|
import WatchlogRUM from 'watchlog-react-rum' | Core SDK: init, bufferEvent, custom, captureError, addBreadcrumb, flush |
| import { useWatchlogRUM } from 'watchlog-react-rum' | React hook for SPA auto-tracking |---
šÆ Event Types
$3
Emitted when a new session begins.`js
{
type: 'session_start',
data: {
name: 'session_start',
referrer: 'https://example.com'
},
context: { / full context / }
}
`$3
Emitted on every route change.`js
{
type: 'page_view',
data: {
name: 'page_view',
navType: 'navigate'
},
context: { / full context / }
}
`$3
Emitted on each page load with navigation and paint metrics.`js
{
type: 'performance',
data: {
name: 'performance',
metrics: {
ttfb: 120,
domLoad: 450,
load: 1200,
domInteractive: 300,
domComplete: 1100
},
navigation: {
redirect: 0,
dns: 10,
tcp: 20,
request: 30,
response: 50,
processing: 800,
load: 100
},
paint: {
fp: 800,
fcp: 850
}
}
}
`$3
Emitted for Web Vitals metrics (CLS, LCP, INP, TTFB, FID, FCP, FP).`js
{
type: 'web_vital',
data: {
name: 'LCP',
value: 1200,
rating: 'good',
id: 'metric-id',
delta: 50
}
}
`$3
Emitted for JavaScript errors, promise rejections, and React component errors.`js
{
type: 'error',
data: {
name: 'window_error',
message: 'Error message',
stack: 'Error stack trace...',
source: 'https://example.com/app.js',
filename: 'app.js',
lineno: 42,
colno: 10,
component: 'MyComponent', // React component errors only
props: { userId: 123 } // React component errors only
}
}
`$3
Emitted for fetch/XHR requests (sampled).`js
{
type: 'network',
data: {
method: 'POST',
url: 'https://api.example.com/users',
status: 200,
ok: true,
duration: 150,
requestSize: 1024,
responseSize: 2048,
transferSize: 2500,
encodedBodySize: 2000,
decodedBodySize: 2048,
timing: {
dns: 10,
tcp: 20,
request: 30,
response: 50,
total: 150
}
}
}
`$3
Emitted for resource loads (images, scripts, stylesheets, etc.).`js
{
type: 'resource',
data: {
name: 'https://example.com/image.jpg',
initiator: 'img',
duration: 200,
transferSize: 50000,
encodedBodySize: 48000,
decodedBodySize: 50000,
renderBlockingStatus: 'non-blocking'
}
}
`$3
Emitted when JavaScript blocks the main thread for >50ms.`js
{
type: 'longtask',
data: {
duration: 120,
startTime: 5000
}
}
`$3
Emitted for user interactions (if enabled, sampled).`js
{
type: 'interaction',
data: {
type: 'click', // 'click', 'scroll', 'submit', 'input'
target: 'button',
value: 'submit-btn'
}
}
`$3
Emitted for custom events.`js
{
type: 'custom',
data: {
name: 'button_clicked',
value: 1,
extra: { buttonId: 'submit' }
}
}
`---
š Context Data
Every event includes rich context information:
`js
{
context: {
apiKey: 'your-api-key',
app: 'your-app',
sessionId: 'sess-abc123',
deviceId: 'dev-xyz789',
environment: 'production',
release: '1.0.0',
page: {
url: 'https://example.com/users/123',
path: '/users/123',
normalizedPath: '/users/:id',
referrer: 'https://google.com',
title: 'User Profile'
},
client: {
userAgent: 'Mozilla/5.0...',
language: 'en-US',
languages: ['en-US', 'en'],
platform: 'MacIntel',
cookieEnabled: true,
onLine: true,
timezone: 'America/New_York',
timezoneOffset: 300,
viewport: {
width: 1920,
height: 1080,
devicePixelRatio: 2
},
screen: {
width: 1920,
height: 1080,
availWidth: 1920,
availHeight: 1040,
colorDepth: 24,
pixelDepth: 24
},
connection: {
effectiveType: '4g',
downlink: 10,
rtt: 50,
saveData: false
},
memory: {
deviceMemory: 8,
hardwareConcurrency: 8
},
browser: {
name: 'Chrome',
version: '120'
},
os: {
name: 'macOS',
version: '14.0'
},
colorScheme: 'dark'
},
breadcrumbs: [
{
category: 'navigation',
message: 'Navigated to /users/:id',
level: 'info',
timestamp: 1234567890
},
// ... more breadcrumbs
]
}
}
``---
š Full product documentation: https://watchlog.io/products/rum
Made with ā¤ļø by the Watchlog team | watchlog.io