vident-rum
v0.14.1
Published
Real User Monitoring SDK for Vident - track sessions, page views, clicks, and Core Web Vitals
Downloads
1,691
Maintainers
Readme
vident-rum
Real User Monitoring SDK for browser applications. Captures page views, clicks, errors, Web Vitals, and session replays.
Installation
npm install vident-rum
# or
pnpm add vident-rum
# or
yarn add vident-rumQuick Start
import { createVidentBrowser } from "vident-rum"
const vident = createVidentBrowser({
apiKey: "vd_...",
serviceName: "my-app",
})
// That's it! Page views, clicks, errors, and vitals are tracked automatically.Configuration
const vident = createVidentBrowser({
// Required
apiKey: "vd_...",
// Optional
serviceName: "my-app", // Service name for filtering
sampleRate: 1.0, // Sample 100% of sessions (default: 1.0)
trackPageViews: true, // Track page views (default: true)
trackClicks: true, // Track clicks (default: true)
trackVitals: true, // Track Web Vitals (default: true)
trackErrors: true, // Track errors (default: true)
trackResources: true, // Track fetch/XHR requests (default: true)
sessionStorage: "cookie", // "cookie" or "sessionStorage" (default: "cookie")
// Source maps
release: "v1.2.3", // Release tag (auto-detected if using withVident/videntPlugin)
// Distributed Tracing
tracing: {
enabled: true, // Inject traceparent headers (default: true)
propagateToOrigins: [ // Origins to propagate trace context to
"https://api.example.com",
/\.example\.com$/,
],
},
// Session Replay (opt-in)
replay: {
enabled: true, // Enable replay recording (default: false)
sampleRate: 0.1, // Record 10% of sessions (default: 0.1)
onErrorSampleRate: 1.0, // Record 100% of sessions with errors (default: 1.0)
privacyMode: "strict", // Privacy mode (default: "strict")
},
})API
vident.trackEvent(name, properties?)
Track a custom event.
vident.trackEvent("purchase", {
productId: "123",
amount: 99.99,
})vident.trackPageView(url?)
Manually track a page view. Called automatically on navigation.
vident.trackPageView("/checkout")vident.trackError(error, options?)
Manually track an error.
try {
await riskyOperation()
} catch (error) {
vident.trackError(error, {
requestId: "req_123",
traceId: "trace_abc",
})
}vident.setUser(id, traits?)
Identify the current user.
vident.setUser("user_123", {
email: "[email protected]",
plan: "pro",
})vident.getSessionId()
Get the current session ID.
const sessionId = vident.getSessionId()vident.forceReplayUpload()
Force replay upload for specific users (bypasses sampling).
// Force upload for VIP users regardless of sample rate
if (user.isVIP) {
vident.forceReplayUpload()
}vident.stopReplay()
Stop replay recording entirely.
vident.isReplayUploading()
Check if replay is currently uploading to server (vs buffering in memory).
Session Replay
Session Replay records user interactions as a video-like playback. It captures DOM mutations, mouse movements, clicks, and scrolls.
Enabling Replay
const vident = createVidentBrowser({
apiKey: "vd_...",
replay: {
enabled: true,
},
})How It Works: Rolling Buffer
Session Replay uses a rolling buffer architecture (similar to Sentry):
- Always recording - When enabled, the SDK always records to a 60-second memory buffer
- Sampled sessions (default 10%) - Upload immediately to server
- Non-sampled sessions (90%) - Keep buffer in memory, zero network cost
- On error - Flush the 60-second buffer and start uploading
This means you always capture what happened before an error, not just after.
Sampling
replay: {
enabled: true,
sampleRate: 0.1, // 10% upload immediately
onErrorSampleRate: 1.0, // 100% upload on error (includes 60s buffer)
}| Scenario | What's Captured | |----------|-----------------| | Sampled (10%) | Full session from start | | Not sampled + error | 60 seconds before error + everything after | | Not sampled + no error | Nothing sent (zero network cost) |
Privacy & Data Protection
Session Replay is designed with privacy as the default. No configuration is required for most applications - sensitive data is automatically protected.
Privacy Modes
| Mode | Text | Inputs | Use Case |
|------|------|--------|----------|
| "strict" (default) | Masked | Masked | Most applications |
| "balanced" | Visible | Masked | Marketing sites, blogs |
| "permissive" | Visible | Visible | Internal tools only |
replay: {
enabled: true,
privacyMode: "strict", // Default - masks everything
}What Each Mode Does
Strict Mode (Default)
All text content is replaced with ****. Form inputs show •••••. You see the page structure and user interactions, but no actual content.
┌─────────────────────────────┐
│ **** ******** │ ← Masked heading
│ │
│ ******** **** ** ******* │ ← Masked paragraph
│ ******* ** *** **** │
│ │
│ Email: [•••••••••••••] │ ← Masked input
│ Password: [••••••••] │
│ │
│ [**********] │ ← Masked button
└─────────────────────────────┘Balanced Mode
Text is visible, but all form inputs are masked. Good for content-heavy sites.
┌─────────────────────────────┐
│ Welcome Back │ ← Visible heading
│ │
│ Please sign in to continue │ ← Visible text
│ │
│ Email: [•••••••••••••] │ ← Masked input
│ Password: [••••••••] │
│ │
│ [Sign In] │ ← Visible button
└─────────────────────────────┘Permissive Mode
Everything is recorded as-is. Only use for internal tools or with explicit user consent.
Fine-Grained Control with HTML Attributes
Regardless of privacy mode, you can control specific elements:
Block Elements Completely
Use data-sw-block to completely hide an element. It will appear as a gray placeholder in replays.
<!-- Credit card form - never record -->
<div data-sw-block>
<input type="text" placeholder="Card number" />
<input type="text" placeholder="CVV" />
</div>
<!-- Sensitive user data -->
<div data-sw-block class="user-profile">
<p>SSN: 123-45-6789</p>
</div>Mask Specific Elements
Use data-sw-mask to mask text while preserving structure (useful in balanced/permissive modes).
<!-- Mask email in a visible section -->
<p>Contact: <span data-sw-mask>[email protected]</span></p>Unmask Elements in Strict Mode
Use data-sw-unmask to show content even when using strict mode.
<!-- Show navigation labels in strict mode -->
<nav data-sw-unmask>
<a href="/">Home</a>
<a href="/products">Products</a>
<a href="/about">About</a>
</nav>
<!-- Show static content -->
<footer data-sw-unmask>
<p>© 2024 My Company</p>
</footer>rrweb Classes (Alternative)
The standard rrweb classes also work:
<div class="rr-block">Hidden content</div>
<div class="rr-mask">Masked content</div>Best Practices
Start with strict mode - It's the safest default. Only relax if needed.
Block sensitive sections entirely - For areas with PII (profile pages, account settings), use
data-sw-block.Review before production - Test your app with replay enabled and verify no sensitive data appears.
Consider user consent - For GDPR compliance, inform users that sessions may be recorded.
Use unmask sparingly - Only unmask truly static, non-sensitive content like navigation labels.
What's Automatically Protected
Even without configuration:
- All
<input>values are masked (except in permissive mode) - Password fields are always masked
- Credit card patterns are detected and masked
<script>content is never recorded<head>metadata is stripped
Sensitive Data Checklist
Before enabling replay, ensure these are blocked or masked:
- [ ] User profile information (name, email, phone)
- [ ] Payment forms and credit card inputs
- [ ] Social security numbers, government IDs
- [ ] Medical or health information
- [ ] Authentication tokens displayed on screen
- [ ] Private messages or chat content
- [ ] Financial account numbers
- [ ] Any PII in confirmation pages
Distributed Tracing
The SDK automatically injects traceparent headers into fetch/XHR requests, connecting frontend sessions to backend traces.
const vident = createVidentBrowser({
apiKey: "vd_...",
tracing: {
enabled: true,
propagateToOrigins: [
"https://api.myapp.com",
/\.myapp\.com$/,
],
},
})Your backend will receive headers like:
traceparent: 00-{traceId}-{spanId}-01Source Maps
Upload source maps so error stack traces show original file names and line numbers instead of minified code.
Next.js
Use withVident() to enable source maps and auto-detect the release:
// next.config.ts
import { withVident } from "vident-rum/next"
export default withVident({
// your Next.js config
})This sets productionBrowserSourceMaps: true and injects NEXT_PUBLIC_VIDENT_RELEASE (from VIDENT_RELEASE, VERCEL_GIT_COMMIT_SHA, or git rev-parse HEAD).
Upload source maps in CI after building:
npx vident-cli sourcemaps upload --api-key $VIDENT_API_KEY .next/staticThe CLI auto-detects the release and url-prefix for .next/static directories.
Vite / TanStack Start / Remix / SvelteKit
Use videntPlugin() — handles everything including upload:
// vite.config.ts
import { videntPlugin } from "vident-rum/vite"
export default defineConfig({
plugins: [
videntPlugin({ apiKey: process.env.VIDENT_API_KEY })
],
})This enables source maps, auto-detects release, uploads maps after build, and deletes .map files from output. No CI step needed.
Release Auto-Detection
The SDK auto-detects the release when not explicitly set:
NEXT_PUBLIC_VIDENT_RELEASE(set bywithVident())import.meta.env.VIDENT_RELEASE(set byvidentPlugin())
// Release auto-detected — no config needed
createVidentBrowser({ apiKey: "vd_..." })
// Or set explicitly
createVidentBrowser({ apiKey: "vd_...", release: "v1.2.3" })CLI Reference
npx vident-cli sourcemaps upload [options] <dir>
Options:
--api-key <key> API key (required)
--release <version> Release tag (auto-detected from env/git)
--url-prefix <prefix> URL prefix (auto-detected for .next/static)
--delete-after-upload Delete .map files after upload
--base-url <url> API base URL (default: https://api.vident.dev)Browser Support
- Chrome 64+
- Firefox 67+
- Safari 12+
- Edge 79+
Session Replay requires:
- MutationObserver
- WeakMap
- requestAnimationFrame
Bundle Size
The base SDK is ~6KB gzipped. Session Replay adds ~63KB gzipped (loaded lazily via dynamic import).
Replay is dynamically imported only when enabled, so it won't affect your bundle if not used.
TypeScript
Full TypeScript support with exported types:
import {
createVidentBrowser,
type BrowserConfig,
type VidentBrowser,
type ReplayConfig,
type PrivacyMode,
type TracingConfig,
} from "vident-rum"Troubleshooting
Replay not recording
- Check that
replay.enabledistrue - Verify you're within the sample rate (try
sampleRate: 1.0for testing) - Check browser console for errors
High data volume
- Reduce
sampleRate(e.g.,0.05for 5%) - Use
"strict"privacy mode (smaller payloads due to masked content)
Missing trace correlation
- Ensure
tracing.propagateToOriginsincludes your API domain - Check that your backend parses the
traceparentheader
License
MIT
