vident-browser
v1.1.0
Published
Real User Monitoring SDK for Vident - track sessions, page views, clicks, and Core Web Vitals
Maintainers
Readme
vident-browser
Real User Monitoring SDK for browser applications. Captures page views, clicks, errors, Web Vitals, and session replays.
Installation
npm install vident-browser
# or
pnpm add vident-browser
# or
yarn add vident-browserQuick Start
import { createVidentBrowser } from "vident-browser"
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)
storage: "sessionStorage", // "sessionStorage" or "cookie" (default: "sessionStorage")
// 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. The userId persists within the session (survives page reloads). Traits are stored on the session and displayed in the user profile dashboard.
vident.setUser("user_123", {
email: "[email protected]",
plan: "pro",
})Privacy note: Traits may contain PII (email, name, etc). Ensure your privacy policy discloses what user data you collect. The SDK does not require cookie consent when using the default
sessionStoragemode.
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 Storage
By default the SDK uses sessionStorage which requires no cookie consent. It is tab-isolated and cleared when the tab closes.
If you need cross-tab session continuity, opt in to cookie mode:
createVidentBrowser({
apiKey: "vd_...",
storage: "cookie", // Requires cookie consent in GDPR regions
})Session Replay
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-browser/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-browser/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-browser"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
