Overview
Session recording captures everything users do on your site—clicks, scrolls, mouse movements, and DOM changes—so you can replay sessions and understand exactly what happened. This is invaluable for debugging issues, improving UX, and understanding user behavior.
MentiQ SDK uses rrweb for high-fidelity session recording with built-in privacy controls.
Installation
Install Dependencies
Install the MentiQ SDK and rrweb:npm install mentiq-sdk rrweb
# or
yarn add mentiq-sdk rrweb
Enable Recording
Set enableSessionRecording: true in your analytics config:import { AnalyticsProvider } from "mentiq-sdk";
function App() {
return (
<AnalyticsProvider
config={{
apiKey: "your-api-key",
projectId: "your-project-id",
enableSessionRecording: true, // Enable automatic recording
}}
>
<YourApp />
</AnalyticsProvider>
);
}
Recording starts automatically when the SDK initializes if enableSessionRecording is enabled.
View Recordings
Access session recordings in your MentiQ dashboard. Each session includes:
- Full replay of user interactions
- Timeline scrubbing
- Session metadata (duration, URL, user ID)
- Associated events and errors
Manual Control
Control recording programmatically with the useAnalytics hook:
import { useAnalytics } from "mentiq-sdk";
function RecordingControls() {
const analytics = useAnalytics();
return (
<div>
<button onClick={() => analytics.startRecording()}>
Start Recording
</button>
<button onClick={() => analytics.stopRecording()}>
Stop Recording
</button>
<button onClick={() => analytics.pauseRecording()}>
Pause
</button>
<button onClick={() => analytics.resumeRecording()}>
Resume
</button>
{analytics.isRecordingActive() && (
<span style={{ color: 'red' }}>🔴 Recording Active</span>
)}
</div>
);
}
Privacy & Data Masking
Session recordings can capture sensitive user data. Use privacy controls to protect user information and ensure GDPR/CCPA compliance.
By default, all input values are masked. This can be customized:
import { SessionRecorder } from "mentiq-sdk";
const recorder = new SessionRecorder(
analytics.config,
analytics.getSessionId(),
{
maskAllInputs: true, // Mask all input fields (default: true)
blockClass: "sensitive", // Block elements with this class
ignoreClass: "no-record", // Ignore these elements entirely
maskTextClass: "private", // Mask text in these elements
}
);
Privacy CSS Classes
Use these CSS classes to control what gets recorded:
Block Elements (Default: mentiq-block)
Completely blocks an element from recording:
<input
type="password"
className="mentiq-block" // Will not be recorded at all
/>
Mask Text (Default: mentiq-mask)
Masks text content while preserving layout:
<div className="mentiq-mask">
User's private information {/* Will appear as "***" */}
</div>
Ignore Elements (Default: mentiq-ignore)
Skips recording this section entirely:
<section className="mentiq-ignore">
{/* This entire section won't be recorded */}
<CreditCardForm />
</section>
function CheckoutPage() {
return (
<form>
{/* Regular fields - recorded normally */}
<input name="email" type="email" />
{/* Masked fields */}
<input
name="address"
className="mentiq-mask"
/>
{/* Completely blocked */}
<div className="mentiq-block">
<input name="card-number" />
<input name="cvv" type="password" />
</div>
</form>
);
}
Configuration Options
Customize recording behavior:
import { SessionRecorder } from "mentiq-sdk";
const recorder = new SessionRecorder(
analytics.config,
analytics.getSessionId(),
{
maxDuration: 10 * 60 * 1000, // Max 10 minutes per recording
checkoutEveryNms: 30 * 1000, // Full snapshot every 30 seconds
blockClass: "sensitive", // Custom block class
maskAllInputs: true, // Mask all inputs
inlineStylesheet: true, // Include styles in recording
collectFonts: true, // Collect font data
sampling: {
mousemove: 50, // Sample mouse movements every 50ms
scroll: 150, // Sample scroll events every 150ms
input: "last", // Only record final input value
},
}
);
Configuration Reference
| Option | Type | Default | Description |
|---|
maxDuration | number | 5 minutes | Maximum recording duration (ms) |
checkoutEveryNms | number | 30 seconds | Full snapshot interval (ms) |
blockClass | string | "mentiq-block" | CSS class to block elements |
ignoreClass | string | "mentiq-ignore" | CSS class to ignore elements |
maskAllInputs | boolean | true | Mask all input values |
maskTextClass | string | "mentiq-mask" | CSS class to mask text |
sampling.mousemove | number | 50 | Mouse movement sample rate (ms) |
sampling.scroll | number | 150 | Scroll sample rate (ms) |
sampling.input | string | "last" | Input sampling ("last" or "all") |
Upload & Storage
Automatic Upload
Recordings are automatically uploaded to your backend:
- Upload Frequency: Every 10 seconds
- Endpoint:
POST /api/v1/sessions/:session_id/recordings
- Final Upload: When recording stops or max duration is reached
From src/session-recording.ts:211-280:
private async uploadRecording(isFinal: boolean = false): Promise<void> {
const endpoint = `${
this.config.endpoint || "https://api.mentiq.io"
}/api/v1/sessions/${this.sessionId}/recordings`;
// Calculate duration from event timestamps
const durationMs = lastTimestamp - firstTimestamp;
const duration = Math.max(1, Math.floor(durationMs / 1000));
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `ApiKey ${this.config.apiKey}`,
"X-Project-ID": this.config.projectId,
},
body: JSON.stringify({
events: eventsToUpload,
duration: duration,
start_url: window.location.href,
user_id: this.config.userId || null,
is_final: isFinal,
event_offset: this.uploadedEventCount,
}),
});
}
Upload Payload
interface RecordingUpload {
events: RRWebEvent[]; // rrweb events
duration: number; // Session duration (seconds)
start_url: string; // Page URL when recording started
user_id: string | null; // Identified user ID
is_final: boolean; // Is this the final upload?
event_offset: number; // Number of events already uploaded
}
Use Cases
Debug User-Reported Issues
function SupportTicketPage({ ticketId }) {
const analytics = useAnalytics();
useEffect(() => {
// Start recording when viewing support tickets
analytics.startRecording();
return () => {
// Stop when leaving the page
analytics.stopRecording();
};
}, [analytics]);
return <TicketDetails ticketId={ticketId} />;
}
Record Critical User Flows
function CheckoutFlow() {
const analytics = useAnalytics();
useEffect(() => {
// Record entire checkout process
analytics.startRecording();
}, []);
const handleCheckoutComplete = () => {
// Stop recording after successful purchase
analytics.stopRecording();
};
return <CheckoutForm onComplete={handleCheckoutComplete} />;
}
Conditional Recording
Only record sessions for specific users or scenarios:
function App() {
const { userPlan } = useAuth();
return (
<AnalyticsProvider
config={{
apiKey: "your-api-key",
projectId: "your-project-id",
// Only record free plan users to understand upgrade friction
enableSessionRecording: userPlan === "free",
}}
>
<YourApp />
</AnalyticsProvider>
);
}
Session recording is optimized for minimal performance impact:
- Events are batched and compressed
- Mouse movements and scrolls are sampled, not recorded continuously
- DOM mutations are efficiently captured using MutationObserver
- Typical overhead: less than 1% CPU, less than 5MB memory
Reduce Recording Size
For better performance on high-traffic sites:
const recorder = new SessionRecorder(config, sessionId, {
sampling: {
mousemove: 100, // Less frequent mouse sampling
scroll: 300, // Less frequent scroll sampling
input: "last", // Only record final values
},
maxDuration: 5 * 60 * 1000, // Shorter max duration
});
Compliance
GDPR/CCPA Requirements:
- Inform users about session recording in your privacy policy
- Provide opt-out mechanisms
- Use privacy classes to mask sensitive data
- Consider requiring explicit consent before recording
- Implement data retention policies
User Consent Example
function App() {
const [hasConsent, setHasConsent] = useState(false);
return (
<>
{!hasConsent && (
<ConsentBanner onAccept={() => setHasConsent(true)} />
)}
<AnalyticsProvider
config={{
apiKey: "your-api-key",
projectId: "your-project-id",
enableSessionRecording: hasConsent,
}}
>
<YourApp />
</AnalyticsProvider>
</>
);
}
Troubleshooting
Recording Not Starting
- Verify rrweb is installed:
npm list rrweb
- Check for console errors with
debug: true
- Ensure
enableSessionRecording: true is set
Missing Styles in Replay
Set inlineStylesheet: true to include CSS in recordings:
const recorder = new SessionRecorder(config, sessionId, {
inlineStylesheet: true,
collectFonts: true,
});
High Memory Usage
Reduce maxDuration or increase checkoutEveryNms to limit event accumulation.