Skip to main content

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

1

Install Dependencies

Install the MentiQ SDK and rrweb:
npm install mentiq-sdk rrweb
# or
yarn add mentiq-sdk rrweb
2

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.
3

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.

Automatic Input Masking

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>

Example: Protecting Sensitive Forms

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

OptionTypeDefaultDescription
maxDurationnumber5 minutesMaximum recording duration (ms)
checkoutEveryNmsnumber30 secondsFull snapshot interval (ms)
blockClassstring"mentiq-block"CSS class to block elements
ignoreClassstring"mentiq-ignore"CSS class to ignore elements
maskAllInputsbooleantrueMask all input values
maskTextClassstring"mentiq-mask"CSS class to mask text
sampling.mousemovenumber50Mouse movement sample rate (ms)
sampling.scrollnumber150Scroll sample rate (ms)
sampling.inputstring"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>
  );
}

Performance Considerations

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
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

  1. Verify rrweb is installed: npm list rrweb
  2. Check for console errors with debug: true
  3. 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.

Build docs developers (and LLMs) love