Real User Monitoring (RUM)
Access the RUM dashboard at https://app.watchlog.io/rum to see real-user traffic, Web Vitals, JavaScript errors, slow resources, and session activity across your applications.
React
Installation
npm install watchlog-react-rum
Optional: For Web Vitals support (CLS, LCP, INP, TTFB, FID):
npm install web-vitals
Quick Start (Recommended: Hook-based with RouterProvider)
For the most accurate route pattern detection (like Datadog), use the wrapped router functions from watchlog-react-rum/react-router-v6:
// 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: <App />,
children: [
{
index: true,
element: <Home />,
},
{
path: 'users/:userId', // This exact pattern will be used for normalization
element: <UserDetail />,
},
{
path: 'posts/:postId',
element: <PostDetail />,
},
],
},
])
// Root component to initialize RUM tracking
function Root() {
useWatchlogRUM({
apiKey: 'YOUR_API_KEY', // Copy from Watchlog panel
endpoint: 'https://api.watchlog.io/rum', // or your self-hosted endpoint
app: 'my-react-app', // Your application name (as shown in the dashboard)
environment: 'production', // optional: 'production' | 'staging' | 'development'
release: '1.0.0', // optional: release version
debug: false, // Enable debug logging
flushInterval: 10000, // Flush interval in milliseconds
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, // Capture LCP/INP/CLS/TTFB (requires web-vitals package)
captureLongTasks: true, // Capture long tasks (>50ms on main thread)
captureFetch: true, // Instrument window.fetch
captureXHR: true, // Instrument XMLHttpRequest
captureUserInteractions: false, // Set to true to enable click/scroll tracking
captureBreadcrumbs: true, // Capture event breadcrumbs
maxBreadcrumbs: 100, // Maximum number of breadcrumbs to keep
beforeSend: (event) => {
// Optional: filter or modify events before sending
// Return null to drop the event
// Example: redact email-like strings from error messages
if (event.type === 'error' && typeof event.data?.message === 'string') {
event.data.message = event.data.message.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+/gi, '[redacted]')
}
// Example: strip query strings from page URLs
if (event.context?.page?.url) {
const u = new URL(event.context.page.url)
u.search = ''
event.context.page.url = u.toString()
}
return event
}
})
return <RouterProvider router={router} />
}
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Root />
</React.StrictMode>
)
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.
Alternative: BrowserRouter Setup
If you're using BrowserRouter instead of RouterProvider, the SDK will still work but will use params-based route reconstruction:
// src/App.jsx
import { BrowserRouter } from 'react-router-dom'
import { useWatchlogRUM } from 'watchlog-react-rum'
function App() {
useWatchlogRUM({
apiKey: 'YOUR_API_KEY',
endpoint: 'https://api.watchlog.io/rum',
app: 'my-react-app',
// ... other config options
})
return (
<BrowserRouter>
{/* Your app routes */}
</BrowserRouter>
)
}
export default App
Manual SDK API (Advanced)
For more control, you can use the SDK directly:
import WatchlogRUM from 'watchlog-react-rum'
// Initialize once at app startup
WatchlogRUM.init({
apiKey: 'YOUR_API_KEY',
endpoint: 'https://api.watchlog.io/rum',
app: 'my-react-app',
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:
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 <div>...</div>
}
Event Types Collected
RUM automatically captures several event types:
- session_start — Emitted when a new session begins
- page_view — Route/page navigations (SPA & traditional)
- session_end — Emitted when session ends (on unload)
- web_vital — LCP / INP / CLS / TTFB / FID values, with attribution when available
- performance — Navigation timing, paint metrics, and resource timing
- resource — Browser resource timings (duration, size, initiator type)
- network — Fetch/XHR requests with detailed timing and size information
- error — JavaScript errors with message, stack, fingerprint and top paths
- longtask — Long-running JavaScript tasks (>50ms on main thread)
- interaction — User interactions (clicks, scrolls, form submissions) if enabled
- custom — Optional, user-defined events (e.g., business actions)
Example payloads
page_view
{
"type": "page_view",
"ts": 1730540000123,
"seq": 1,
"context": {
"apiKey": "your-api-key",
"app": "my-react-app",
"sessionId": "sess-abc123",
"deviceId": "dev-xyz789",
"environment": "production",
"release": "1.0.0",
"page": {
"url": "https://site.com/user/42",
"path": "/user/42",
"normalizedPath": "/user/:userId",
"referrer": "https://google.com",
"title": "User Profile"
},
"client": {
"userAgent": "Mozilla/5.0 ...",
"language": "en-US",
"viewport": { "width": 1920, "height": 1080, "devicePixelRatio": 2 },
"browser": { "name": "Chrome", "version": "120" },
"os": { "name": "macOS", "version": "14.0" }
}
},
"data": {
"name": "page_view",
"navType": "navigate"
}
}
web_vital
{
"type": "web_vital",
"ts": 1730540000456,
"seq": 2,
"context": { /* full context */ },
"data": {
"name": "LCP",
"value": 1835.42,
"rating": "good",
"id": "metric-id",
"delta": 50
}
}
resource
{
"type": "resource",
"ts": 1730540000789,
"seq": 3,
"context": { /* full context */ },
"data": {
"name": "https://cdn.example.com/app.js",
"initiator": "script",
"duration": 342.1,
"transferSize": 128764,
"encodedBodySize": 120000,
"decodedBodySize": 128764
}
}
error
{
"type": "error",
"ts": 1730540000123,
"seq": 4,
"context": { /* full context */ },
"data": {
"name": "window_error",
"message": "TypeError: Cannot read properties of undefined",
"stack": "at MyComponent (App.jsx:42:10)\n at ...",
"source": "https://example.com/app.js",
"filename": "app.js",
"lineno": 42,
"colno": 10,
"component": "MyComponent",
"props": { "userId": 123 }
}
}
network
{
"type": "network",
"ts": 1730540000123,
"seq": 5,
"context": { /* full context */ },
"data": {
"method": "POST",
"url": "https://api.example.com/users",
"status": 200,
"ok": true,
"duration": 150,
"requestSize": 1024,
"responseSize": 2048,
"transferSize": 2500,
"timing": {
"dns": 10,
"tcp": 20,
"request": 30,
"response": 50,
"total": 150
}
}
}
Note: You can intercept/modify any event in
beforeSend. Returnnullto drop it entirely.
Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
app | string | required | Application name shown in the dashboard. |
apiKey | string | required | Copy from RUM → Your RUM Credentials in the panel. |
endpoint | string | required | Collector endpoint (cloud or self-hosted). |
environment | string | "prod" | Environment tag (e.g., prod/staging/dev). |
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 (0.0–1.0) | 1.0 | Fraction of sessions to record. Note: Maximum allowed value is 0.5 (50%) to prevent server overload. Values above 0.5 will be automatically capped. |
networkSampleRate | number (0.0–1.0) | 0.1 | Sampling for resource/fetch/xhr events. Recommended: 0.1 (10%) for production. |
interactionSampleRate | number (0.0–1.0) | 0.1 | Sampling for user interactions (clicks, scrolls). Recommended: 0.1 (10%) for production. |
enableWebVitals | boolean | true | Capture LCP/INP/CLS/TTFB/FID (requires web-vitals package). |
autoTrackInitialView | boolean | true | Send first page_view on mount. |
captureLongTasks | boolean | true | Capture long tasks (>50ms on main thread). |
captureFetch | boolean | true | Instrument window.fetch. |
captureXHR | boolean | true | Instrument 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 | (ev) => ev | null | undefined | Mutate, enrich, or drop events before export. |
Sample Rate Limits & Best Practices
Session Sample Rate (sampleRate)
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 strain
Why 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
Network Sample Rate (networkSampleRate)
Network requests can be very frequent. We recommend keeping this at 0.1 (10%) or lower for production environments.
Interaction Sample Rate (interactionSampleRate)
User interactions (clicks, scrolls) can be extremely frequent. We recommend 0.1 (10%) or lower for production.
Privacy & Redaction
Use beforeSend to drop sensitive events or redact PII. Consider:
- Strip query strings or hashes from URLs
- Remove email addresses, phone numbers, or form values from error messages
- Lower sampling for high-traffic pages to reduce data volume
- Filter out internal/admin routes
Troubleshooting
- No data in the dashboard? Confirm
endpoint,apiKey, andappmatch the panel values. - SPAs not tracking route changes? Ensure you're using
useWatchlogRUMhook or manually tracking route changes. - Routes not normalizing correctly? Use
createBrowserRouterfromwatchlog-react-rum/react-router-v6for accurate route pattern extraction. - Web Vitals missing? Install
web-vitalsand keepenableWebVitals: true. - Browser blocks third-party scripts? Verify CORS and that your collector domain is reachable from clients.
- Too much data? Lower
sampleRate/networkSampleRate, or drop events viabeforeSend.
Vue
Works with Vue 3 and Vue Router v4.
Installation
npm install @watchlog/rum-vue
Optional: For Web Vitals support (CLS, LCP, INP, TTFB, FID):
npm install web-vitals
Quick Start (Plugin-based - Recommended)
Use the createWatchlogRUMPlugin helper in your main.js to initialize tracking globally:
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createWatchlogRUMPlugin } from '@watchlog/rum-vue'
const app = createApp(App)
app.use(router)
app.use(createWatchlogRUMPlugin({
router, // Vue Router instance (required for route tracking)
apiKey: 'YOUR_API_KEY', // Copy from Watchlog panel
endpoint: 'https://api.watchlog.io/rum', // or your self-hosted endpoint
app: 'my-vue-app', // Your application name (as shown in the dashboard)
environment: 'production', // optional: 'production' | 'staging' | 'development'
release: '1.0.0', // optional: release version
debug: false, // Enable debug logging
flushInterval: 10000, // Flush interval in milliseconds
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, // Capture LCP/INP/CLS/TTFB (requires web-vitals package)
captureLongTasks: true, // Capture long tasks (>50ms on main thread)
captureFetch: true, // Instrument window.fetch
captureXHR: true, // Instrument XMLHttpRequest
captureUserInteractions: false, // Set to true to enable click/scroll tracking
captureBreadcrumbs: true, // Capture event breadcrumbs
maxBreadcrumbs: 100, // Maximum number of breadcrumbs to keep
beforeSend: (event) => {
// Optional: filter or modify events before sending
// Return null to drop the event
// Example: redact email-like strings from error messages
if (event.type === 'error' && typeof event.data?.message === 'string') {
event.data.message = event.data.message.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+/gi, '[redacted]')
}
// Example: strip query strings from page URLs
if (event.context?.page?.url) {
const u = new URL(event.context.page.url)
u.search = ''
event.context.page.url = u.toString()
}
return event
}
}))
app.mount('#app')
This automatically sends:
session_starton first load (with normalized path and referrer)page_viewon every route changesession_endon unloaderrorfor uncaught JS errors, unhandled promise rejections, and Vue component errorsperformancemetrics on each page loadweb_vitalmetrics (CLS, LCP, INP, TTFB, FID, FCP, FP)networkrequests (fetch/XHR) with detailed timingresourceloads (images, scripts, stylesheets, etc.)longtaskevents when JavaScript blocks the main threadinteractionevents (if enabled)
Alternative: Composable Setup
If you prefer to initialize RUM manually in your root component:
// App.vue (script setup)
<script setup>
import { useWatchlogRUM } from '@watchlog/rum-vue'
const { rum, custom, captureError } = useWatchlogRUM({
apiKey: 'YOUR_API_KEY',
endpoint: 'https://api.watchlog.io/rum',
app: 'my-vue-app',
debug: true,
flushInterval: 5000,
captureUserInteractions: true,
})
// Manually capture errors
const handleError = () => {
try {
// your code
} catch (error) {
captureError(error, { component: 'MyComponent' })
}
}
// Send custom events
const handleClick = () => {
custom('button_clicked', 1, { buttonId: 'submit-btn' })
}
</script>
Manual SDK API (Advanced)
For more control, you can use the SDK directly:
import WatchlogRUM from '@watchlog/rum-vue'
// Initialize once at app startup
WatchlogRUM.init({
apiKey: 'YOUR_API_KEY',
endpoint: 'https://api.watchlog.io/rum',
app: 'my-vue-app',
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)
Event Types Collected
Same as React SDK. RUM automatically captures:
- session_start — Emitted when a new session begins
- page_view — Route/page navigations (SPA & traditional)
- session_end — Emitted when session ends (on unload)
- web_vital — LCP / INP / CLS / TTFB / FID values
- performance — Navigation timing, paint metrics, and resource timing
- resource — Browser resource timings (duration, size, initiator type)
- network — Fetch/XHR requests with detailed timing and size information
- error — JavaScript errors with message, stack, fingerprint and top paths
- longtask — Long-running JavaScript tasks (>50ms on main thread)
- interaction — User interactions (clicks, scrolls, form submissions) if enabled
- custom — Optional, user-defined events (e.g., business actions)
What It Sends (Wire Format)
The Vue SDK batches events and POSTs a wrapper to /rum:
{
"apiKey": "your-api-key",
"app": "my-vue-app",
"sdk": "watchlog-rum-vue",
"version": "0.3.0",
"sentAt": 1730540000000,
"sessionId": "sess-abc123",
"deviceId": "dev-xyz789",
"environment": "production",
"release": "1.0.0",
"events": [
{
"type": "page_view",
"ts": 1730540000123,
"seq": 1,
"context": {
"page": {
"url": "https://site.com/user/42",
"path": "/user/42",
"normalizedPath": "/user/:id",
"referrer": ""
},
"client": {
"userAgent": "Mozilla/5.0 …",
"language": "en-US",
"timezone": "Europe/Amsterdam"
},
"app": "my-vue-app",
"environment": "production",
"sessionId": "sess-abc123",
"deviceId": "dev-xyz789"
},
"data": {
"name": "page_view"
}
}
]
}
The server-side accepts
apiKey/appfrom either headers or wrapper body. Events includecontextanddata(used for Mongo/Influx mapping).
Vue Configuration Reference
Same as React where applicable. Additional notes:
app,apiKey,endpoint— requiredrouter— required when usingcreateWatchlogRUMPluginfor automatic route trackingenvironment,release— optional labels shown in dashboards and used for filteringbeforeSend— mutate or drop events before export- Router detection uses normalized paths (e.g.,
/user/:id)
Sample Rate Limits & Best Practices
Same as React SDK. See the React section above for detailed recommendations.
Privacy & Redaction
Use beforeSend to drop sensitive events or redact PII. Consider:
- Strip query strings or hashes from URLs
- Remove email addresses, phone numbers, or form values from error messages
- Lower sampling for high-traffic pages to reduce data volume
- Filter out internal/admin routes
Troubleshooting (Vue)
- 400 "Missing apiKey or app": Ensure both are present in the wrapper or
X-Watchlog-Keyheader. - Empty
context/data: You may be using an older version. Update to the latest@watchlog/rum-vuewhich wraps events and setscontext/dataper event. - No navigations captured: Confirm the router instance is passed to the plugin or
useWatchlogRUMis invoked after router creation. - Routes not normalizing correctly: Ensure your Vue Router routes use dynamic segments (e.g.,
/user/:idinstead of/user/:userIdif your route definition uses:userId). - Web Vitals missing: Install
web-vitalsand keepenableWebVitals: true.
Next steps
- Open the dashboard: https://app.watchlog.io/rum
- Copy your Endpoint and API Key from Connect to RUM.
- Deploy to your staging or production environment.
- Explore: Overview → Web Vitals → Errors → Resources → Sessions.
