rrweb: The Revolutionary Web Session Recorder
Stop guessing what your users experienced. Start seeing exactly what they saw.
Every developer has faced this nightmare: a customer reports a critical bug, but you can't reproduce it. You ask for screenshots, browser versions, steps to reproduce—yet the issue remains elusive. What if you could press "play" and watch exactly what happened? Enter rrweb, the open-source powerhouse that records and replays web interactions with pixel-perfect precision.
This comprehensive guide reveals why developers worldwide are abandoning traditional debugging methods for this game-changing tool. You'll discover how rrweb captures every DOM mutation, mouse movement, and user interaction, transforming vague bug reports into crystal-clear visual evidence. From installation to advanced implementations, we'll walk through real code examples, performance optimizations, and battle-tested strategies that make rrweb an essential addition to your developer toolkit.
What is rrweb?
rrweb—short for "record and replay the web"—is a sophisticated open-source library that captures user interactions on web applications and reconstructs them as playable sessions. Unlike traditional logging or analytics tools that capture abstract events, rrweb serializes the entire DOM state and records incremental mutations, enabling pixel-perfect replay of user sessions.
Born from the frustration of debugging elusive frontend issues, rrweb has evolved into a robust ecosystem maintained by an active community of contributors. The project gained significant traction after its creator, yz-yu, demonstrated its potential through a compelling documentary (available with English subtitles) that showcases the technical architecture and real-world impact. With over 15,000 stars on GitHub and backing from major sponsors, rrweb has become the go-to solution for developers who need more than just stack traces.
The project's momentum is fueled by its unique approach: instead of relying on video recordings that consume massive bandwidth, rrweb captures the structural changes to your application's DOM. This results in lightweight, searchable, and programmatically accessible session data. The community thrives on Slack, Twitter, and Reddit, where developers share implementations, troubleshoot issues, and contribute to the roadmap.
The Three-Pillar Architecture
rrweb's power lies in its modular design, consisting of three core components that work in harmony:
1. rrweb-snapshot: This foundational package handles the heavy lifting of DOM serialization. It converts the entire document object model into a compact, serializable data structure with unique identifiers for each node. When it's time to replay, the rebuilding feature reconstructs this snapshot back into a functional DOM tree. This bidirectional conversion is what makes rrweb's recordings so lightweight yet accurate.
2. rrweb: The heart of the system, providing two essential functions. The record function observes all DOM mutations, user inputs, and viewport changes using a sophisticated patch-based system. The replay function takes this stream of mutations and reconstructs the session chronologically, respecting original timestamps to maintain authentic timing.
3. rrweb-player: A polished UI layer that transforms raw replay data into an interactive video-like experience. It provides intuitive controls including play/pause, speed adjustment, timeline scrubbing, and event markers. This makes session review feel natural, even for non-technical stakeholders.
Key Features That Set rrweb Apart
🎯 Pixel-Perfect DOM Serialization
rrweb doesn't just capture screenshots—it captures structure. The snapshotting engine traverses the entire DOM tree, preserving CSS styles, canvas content, and dynamic states. Each element receives a unique ID, enabling precise tracking of modifications. This approach captures not just what users see, but the underlying structure that traditional screen recording tools miss.
⚡ Incremental Mutation Recording
Instead of capturing full page states multiple times per second, rrweb intelligently records only what changes. When a user clicks a button, types in a field, or scrolls the page, rrweb captures the specific mutation event. This incremental approach reduces data size by up to 95% compared to video-based solutions, making long-session recording practical and storage-efficient.
🎛️ Interactive Replayer with GUI Controls
The rrweb-player package delivers a premium replay experience that rivals commercial solutions. Developers can pause at critical moments, adjust playback speed from 0.5x to 8x, and jump to specific timestamps. Event markers on the timeline highlight clicks, inputs, and navigation events, making it trivial to locate important interactions.
🔒 Sandboxed Replay Environment
Security-conscious developers appreciate rrweb's sandboxed replay system. Recorded sessions execute in an isolated iframe environment, preventing malicious code from affecting the host page. This architecture ensures that even if a recorded session contains compromised scripts, your replay environment remains secure.
📦 Lightweight Bundle Sizes
Performance matters. The recorder clocks in at approximately 35KB gzipped, while the replayer adds another 30KB. This lean footprint means you can deploy rrweb without significantly impacting your application's load time. The modular architecture lets you load only the components you need, further optimizing resource usage.
🔌 Extensible Plugin Architecture
The upcoming plugin API will unlock powerful integrations. The roadmap includes dedicated plugins for XHR requests, fetch calls, and GraphQL operations. This means you'll soon replay not just visual interactions, but the complete network context that surrounded them—transforming rrweb from a visual tool into a comprehensive debugging platform.
🛡️ Privacy-First Design
rrweb provides built-in mechanisms to mask sensitive data. Developers can configure the recorder to ignore specific DOM elements, mask text inputs, or exclude certain event types. This makes it compliant with GDPR and privacy regulations while still capturing valuable debugging information.
Real-World Use Cases That Transform Workflows
1. Bug Reproduction That Actually Works
Traditional bug reports are fundamentally broken. Users describe what they think happened, but developers need to see what actually happened. With rrweb, when a user encounters an error, you receive a complete visual recording of their session. You can watch the exact sequence of clicks, form inputs, and page transitions that led to the issue. This eliminates the back-and-forth of "Can you try clearing your cache?" and reduces mean time to resolution by over 70%.
Implementation pattern: Attach rrweb recording to your error reporting service. When an exception occurs, stop the recording and send the session data along with the stack trace. Tools like Sentry integrate seamlessly with this approach.
2. User Experience Research Without Guessing
Product teams often rely on analytics dashboards that show what users did, but not how they struggled. rrweb reveals the human story behind the metrics. Watch as users rage-click unresponsive buttons, abandon complex forms, or miss critical CTAs. These insights drive data-informed design decisions that genuinely improve conversion rates.
Implementation pattern: Record 5% of user sessions automatically, then analyze them using rrweb-player's event timeline. Look for patterns like repeated clicks, excessive scrolling, or rapid back-and-forth navigation that indicate confusion.
3. Customer Support That Delivers Answers
Support teams waste countless hours trying to understand customer issues through text descriptions. With rrweb, support agents can watch exactly what the customer experienced, complete with their inputs and navigation path. This enables faster, more accurate resolutions and dramatically improves customer satisfaction scores.
Implementation pattern: Implement a "Share Session" button in your help widget. When clicked, it captures the last 2 minutes of activity and generates a shareable link. Support agents access this through a private dashboard powered by rrweb-player.
4. Security Audit Trails with Visual Evidence
For compliance-sensitive applications, rrweb provides immutable visual records of user actions. Financial institutions use it to verify transaction authenticity. Healthcare platforms document patient portal interactions. The sandboxed replay ensures audit trails remain tamper-proof while providing investigators with complete context.
Implementation pattern: Record all sessions for authenticated users, storing data in encrypted, append-only storage. Use rrweb's masking features to automatically redact PII while preserving interaction patterns for forensic analysis.
Step-by-Step Installation & Setup Guide
Prerequisites
Before installing rrweb, ensure you have:
- Node.js 14+ and npm/yarn
- A modern web application (React, Vue, Angular, or vanilla JS)
- Basic understanding of DOM manipulation
Installation
The rrweb team strongly recommends using Yarn over npm for dependency management due to the monorepo structure.
# Install the core recorder package
yarn add rrweb
# Install the player UI (optional, for replay functionality)
yarn add rrweb-player
# For TypeScript projects, types are included automatically
Basic Recording Setup
Initialize rrweb in your application's entry point:
import { record } from 'rrweb';
// Start recording user interactions
const stopRecording = record({
emit(event) {
// Send event to your storage solution
sendEventToServer(event);
},
// Mask sensitive input fields
maskInputOptions: {
password: true,
creditCard: true
},
// Sample 50% of sessions to reduce load
sampling: {
mousemove: 50,
scroll: 50
}
});
// Later, when you want to stop recording
// stopRecording();
Setting Up the Replayer
Create a dedicated replay page in your application:
import { Replayer } from 'rrweb';
import 'rrweb-player/dist/style.css';
// Fetch recorded events from your storage
const events = await fetchSessionEvents(sessionId);
// Initialize the replayer in your container
const replayer = new Replayer(events, {
root: document.getElementById('replay-container'),
// Configure playback behavior
speed: 1,
maxScale: 2,
skipInactive: true
});
// Add player controls
replayer.play();
Configuration Best Practices
For production deployments, configure these essential options:
record({
emit(event) {
// Buffer events and send in batches to reduce network overhead
eventBuffer.push(event);
if (eventBuffer.length > 100) {
flushEvents();
}
},
// Exclude your own monitoring scripts from recordings
blockClass: 'rrweb-block',
// Mask sensitive data
maskTextClass: 'rrweb-mask',
// Limit recording length to prevent memory issues
packFn: (event) => {
// Custom compression logic
return JSON.stringify(event);
}
});
Environment-Specific Setup
Development: Use the REPL tool included in the rrweb repository for rapid testing.
# Clone the repository
git clone https://github.com/rrweb-io/rrweb.git
cd rrweb
# Install dependencies for all packages
yarn install
# Build all packages
yarn build:all
# Start development mode with auto-rebuild
yarn dev
Production: Deploy the pre-built UMD bundles via CDN for optimal caching:
<!-- Recorder -->
<script src="https://cdn.jsdelivr.net/npm/@rrweb/record@latest/umd/record.min.js"></script>
<!-- Player -->
<script src="https://cdn.jsdelivr.net/npm/@rrweb/replay@latest/umd/replay.min.js"></script>
REAL Code Examples from the Repository
Let's examine actual implementation patterns extracted from the rrweb codebase and documentation.
Example 1: Basic Recording with Event Buffering
This pattern shows how to efficiently capture and transmit recording data without overwhelming your network:
// Initialize an event buffer to batch transmissions
const eventBuffer = [];
const BUFFER_SIZE = 50;
const FLUSH_INTERVAL = 5000; // 5 seconds
// Start recording with custom emit function
const stopFn = rrweb.record({
emit(event) {
// Add timestamp for synchronization
event.timestamp = Date.now();
// Add to buffer
eventBuffer.push(event);
// Flush when buffer reaches threshold
if (eventBuffer.length >= BUFFER_SIZE) {
flushEvents();
}
},
// Mask all password inputs automatically
maskInputOptions: {
password: true
},
// Sample mouse movements to reduce data volume
sampling: {
mousemove: true,
mousemoveCallback: (events) => {
// Only keep every 3rd mousemove event
return events.filter((_, index) => index % 3 === 0);
}
}
});
// Periodic flush for remaining events
setInterval(() => {
if (eventBuffer.length > 0) {
flushEvents();
}
}, FLUSH_INTERVAL);
// Function to send events to server
function flushEvents() {
if (eventBuffer.length === 0) return;
const eventsToSend = eventBuffer.splice(0, eventBuffer.length);
fetch('/api/sessions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
sessionId: getCurrentSessionId(),
events: eventsToSend,
userAgent: navigator.userAgent
})
}).catch(error => {
// Re-queue events on failure
eventBuffer.unshift(...eventsToSend);
console.error('Failed to send events:', error);
});
}
Explanation: This implementation demonstrates production-ready event handling. The buffering strategy prevents network congestion by batching events. The sampling configuration reduces mousemove data by 66% while maintaining visual fidelity. Automatic password masking ensures compliance, and the retry logic guarantees no data loss during network hiccups.
Example 2: Replayer with Custom Event Markers
Enhance the replay experience by adding visual markers for important events:
import { Replayer, mirror } from 'rrweb';
// Assume we have events from a recorded session
const sessionEvents = await fetchSessionEvents('session-123');
// Create a custom configuration for the replayer
const replayer = new Replayer(sessionEvents, {
root: document.getElementById('replay-container'),
// Skip periods of user inactivity
skipInactive: true,
// Maximum scale for zooming
maxScale: 2,
// Custom handler for playback events
triggerFocus: true,
// Inject custom styles into the replay iframe
insertStyleRules: [
'.custom-marker { background: #ff6b6b; border-radius: 50%; }'
]
});
// Add event markers to the timeline
const importantEvents = sessionEvents.filter(event =>
event.type === 3 || // Click events
event.type === 5 // Input events
);
importantEvents.forEach(event => {
const marker = document.createElement('div');
marker.className = 'timeline-marker';
marker.style.left = `${(event.timestamp / sessionDuration) * 100}%`;
marker.title = `Event at ${formatTime(event.timestamp)}`;
marker.addEventListener('click', () => {
replayer.pause();
replayer.goto(event.timestamp - 1000); // Go 1 second before event
});
document.getElementById('timeline').appendChild(marker);
});
// Start playback with custom speed
replayer.play(2); // 2x speed
// Listen for specific events during replay
replayer.on('event-cast', (event) => {
if (event.type === 5) { // Input event
console.log('User typed:', event.data.text);
// Could trigger analytics or logging here
}
});
Explanation: This advanced example shows how to build a custom replay interface. The skipInactive option fast-forwards through idle periods. Event markers provide visual navigation to critical moments. The triggerFocus option ensures the replay iframe receives proper focus for accurate event simulation. By listening to event-cast, you can synchronize external analytics with the replay.
Example 3: TypeScript Implementation with Strong Typing
Leverage rrweb's TypeScript definitions for type-safe recording:
import { record, Replayer, eventWithTime, recordOptions } from 'rrweb';
// Define custom event types for your application
interface CustomUserEvent {
type: 'user-identified';
userId: string;
timestamp: number;
}
// Type-safe recording configuration
const recordConfig: recordOptions<eventWithTime | CustomUserEvent> = {
emit(event) {
// TypeScript knows the exact shape of event
if ('type' in event && event.type === 'user-identified') {
console.log('User identified:', event.userId);
}
// Send to typed storage interface
storeEvent(event as eventWithTime);
},
// Strongly typed masking options
maskInputOptions: {
password: true,
'credit-card': true,
'cc-number': true
},
// Type-checked sampling configuration
sampling: {
mousemove: 50, // Sample 50% of mouse movements
scroll: 50, // Sample 50% of scroll events
media: 0 // Record all media interactions
},
// Block specific CSS classes from recording
blockClass: 'rrweb-block',
maskTextClass: 'rrweb-mask',
// Custom pack function for compression
packFn: (event: eventWithTime) => {
return JSON.stringify({
...event,
// Add custom metadata
appVersion: process.env.APP_VERSION
});
}
};
// Initialize recording with full type safety
const stopRecording: () => void = record(recordConfig);
// Type-safe replayer implementation
class SessionReplayer {
private replayer: Replayer;
private events: eventWithTime[];
constructor(events: eventWithTime[], container: HTMLElement) {
this.events = events;
this.replayer = new Replayer(events, {
root: container,
loadTimeout: 10000,
showWarning: true,
showDebug: process.env.NODE_ENV === 'development'
});
}
public playToEvent(eventId: number): void {
const targetEvent = this.events.find(e => e.timestamp === eventId);
if (targetEvent) {
this.replayer.goto(targetEvent.timestamp);
this.replayer.play();
}
}
public getDuration(): number {
return this.replayer.getMetaData().totalTime;
}
}
Explanation: This TypeScript example demonstrates rrweb's excellent type support. The recordOptions generic allows extending event types with application-specific metadata. Type checking prevents configuration errors at compile-time. The SessionReplayer class encapsulates replay logic with proper method signatures, making it maintainable and testable. The packFn option shows how to inject custom metadata for version tracking.
Advanced Usage & Best Practices
Performance Optimization Strategies
Smart Sampling: Don't record everything. Use rrweb's sampling configuration to reduce overhead:
record({
sampling: {
mousemove: 20, // Only 20% of mouse movements
scroll: 30, // Only 30% of scroll events
media: 0 // Capture all media events
}
});
Event Buffering: Always implement client-side buffering to batch network requests. This prevents performance degradation during rapid user interactions.
Worker Thread Recording: For CPU-intensive applications, consider running rrweb in a Web Worker to keep the main thread responsive:
// In your main thread
const worker = new Worker('rrweb-worker.js');
worker.postMessage({ type: 'startRecording' });
// In the worker
self.importScripts('https://cdn.jsdelivr.net/npm/rrweb@latest/rrweb.min.js');
self.onmessage = (event) => {
if (event.data.type === 'startRecording') {
rrweb.record({
emit: (e) => self.postMessage(e)
});
}
};
Privacy and Compliance
Automatic PII Masking: Configure rrweb to automatically mask sensitive information:
record({
maskInputOptions: {
password: true,
email: true,
tel: true
},
maskTextClass: 'mask-sensitive',
blockClass: 'block-sensitive'
});
Selective Recording: Only record sessions from opted-in users or when debugging specific issues:
const shouldRecord = userHasConsented() || isBugReportMode();
if (shouldRecord) {
const stopFn = record({ /* config */ });
// Store stopFn for later cleanup
}
Storage and Retrieval Patterns
Compression: Recordings can grow large. Implement compression on the client:
import pako from 'pako';
record({
packFn: (event) => {
const compressed = pako.deflate(JSON.stringify(event));
return btoa(String.fromCharCode(...compressed));
}
});
Incremental Loading: For long sessions, implement progressive loading:
// Load first 1000 events, then fetch more as needed
const initialEvents = await fetchEvents(sessionId, { limit: 1000 });
const replayer = new Replayer(initialEvents);
replayer.on('progress', (currentTime) => {
if (currentTime > loadedDuration * 0.8) {
loadMoreEvents();
}
});
Comparison with Alternatives
| Feature | rrweb | LogRocket | FullStory | Sentry Session Replay |
|---|---|---|---|---|
| Cost | Free (Open Source) | $99+/month | Custom Pricing | $26+/month |
| Self-Hosting | ✅ Yes | ❌ No | ❌ No | ❌ No |
| Bundle Size | ~35KB (Recorder) | ~50KB | ~45KB | ~40KB |
| Data Ownership | Full Control | Vendor Cloud | Vendor Cloud | Vendor Cloud |
| Customization | Unlimited | Limited | Limited | Limited |
| Plugin System | ✅ Yes (Upcoming) | ❌ No | ❌ No | ❌ No |
| TypeScript Support | ✅ First-class | ✅ Yes | ❌ No | ✅ Yes |
| Community | Active Open Source | Vendor Support | Vendor Support | Vendor Support |
Why Choose rrweb?
For Startups: The zero-cost entry point and self-hosting capability make rrweb ideal for budget-conscious teams. You can deploy on your existing infrastructure without monthly fees scaling with user growth.
For Enterprises: Data sovereignty is non-negotiable. rrweb keeps sensitive user sessions within your security perimeter. The plugin architecture allows deep integration with internal systems and custom analytics pipelines.
For Developers: Unlike black-box commercial solutions, rrweb's open-source nature means you can debug the recorder itself when issues arise. The TypeScript codebase is well-documented and extensible.
For Privacy-Focused Products: With rrweb, you implement your own privacy controls. No third-party scripts, no data sharing, complete compliance control.
Frequently Asked Questions
Q: How does rrweb impact application performance? A: The recorder adds minimal overhead—typically 2-5% CPU usage during active recording. Memory usage grows linearly with session length but is capped by the buffering strategy. Most applications see no perceptible performance degradation.
Q: Can rrweb record cross-origin iframes? A: Yes, but with limitations. Same-origin iframes are recorded automatically. For cross-origin iframes, you must install rrweb in the iframe content and coordinate event merging. The upcoming plugin system will simplify this process.
Q: How do I handle very long sessions (30+ minutes)? A: Implement session segmentation. Stop and restart recording every 10 minutes, storing each segment separately. During replay, chain the segments together. This prevents memory bloat and makes storage management easier.
Q: Is rrweb production-ready? A: Absolutely. Companies like PostHog, Highlight.io, and numerous Fortune 500 enterprises use rrweb in production. The project follows semantic versioning, maintains comprehensive test coverage, and has a responsive security policy.
Q: How do I mask dynamic content that isn't covered by CSS classes?
A: Use the maskInputFn and maskTextFn options to implement custom masking logic:
record({
maskTextFn: (text) => {
// Mask email addresses
return text.replace(/[\w.-]+@[\w.-]+\.\w+/g, '[EMAIL]');
}
});
Q: Can I export recordings as video files?
A: While rrweb doesn't natively export video, you can use the rrvideo utility to convert recordings to MP4 format. This is useful for sharing with stakeholders who don't have access to the replay tool.
Q: How does rrweb handle Single Page Applications (SPAs)? A: rrweb excels with SPAs. It automatically detects URL changes through the History API and captures DOM mutations from framework re-renders. No special configuration is needed for React, Vue, or Angular applications.
Conclusion: The Future of Web Debugging is Visual
rrweb represents a paradigm shift in how we understand user behavior and debug web applications. By transforming ephemeral user sessions into reproducible, analyzable data, it eliminates the guesswork that has plagued frontend development for decades. The combination of lightweight recording, pixel-perfect replay, and open-source flexibility makes it an indispensable tool for modern development teams.
What sets rrweb apart isn't just its technical sophistication—it's the philosophy that developers deserve complete control over their debugging tools. While commercial solutions lock you into proprietary ecosystems and recurring costs, rrweb empowers you to build exactly what your team needs, hosted where you want, customized how you like.
The active community, comprehensive documentation, and ambitious roadmap (featuring plugin APIs and advanced compression) ensure rrweb will continue evolving. Whether you're debugging a tricky React bug, optimizing conversion funnels, or building compliance audit trails, rrweb delivers capabilities that were once exclusive to enterprise budgets.
Ready to revolutionize your debugging workflow? Visit the official rrweb GitHub repository to get started. Star the project, join the Slack community, and start recording your first session today. Your future self—staring at a cryptic bug report—will thank you.
The web is meant to be replayed. With rrweb, it finally can be.