Session Replay

Record and replay user sessions to understand exactly what happened before, during, and after errors. Privacy-first by default -- all text and inputs are masked until you explicitly unmask them.

Quick Start

Install the replay package and pass it as an addon to the browser SDK.

npm install @tracekit/replay
import { init } from '@tracekit/browser';
import { replayIntegration } from '@tracekit/replay';

init({
  apiKey: 'your-api-key',
  addons: [replayIntegration()],
});

Session replay captures DOM changes, network requests, and console output. All text content and input values are masked by default -- no sensitive data leaves the browser unless you explicitly configure unmasking.

How It Works

  1. 1
    Recording starts on init(). The replay integration hooks into the browser SDK lifecycle. When init() is called, the recorder captures an initial DOM snapshot and begins observing mutations, scroll, and resize events.
  2. 2
    Events are buffered and compressed. DOM mutations, network requests, and console output are collected in a buffer. A Web Worker gzip-compresses the data in the background (with a main-thread fallback).
  3. 3
    Chunks are uploaded periodically. Every 30 seconds (configurable via flushInterval), compressed chunks are uploaded to TraceKit. On tab close, sendBeacon is used as a last-resort upload.
  4. 4
    Playback reconstructs the session. The dashboard decompresses chunks and reconstructs the DOM in an iframe. Network requests and console output are displayed in synchronized side panels.

Performance impact: Replay uses mutation observers (not polling) and compresses data in a Web Worker. Typical overhead is less than 1% CPU on modern browsers. The SDK never crashes the host application -- all replay code is wrapped in try/catch.

Configuration Reference

Pass a ReplayConfig object to replayIntegration(). All options are optional -- defaults are privacy-first and production-ready.

Annotated Configuration

import { replayIntegration } from '@tracekit/replay';

const replay = replayIntegration({
  // Sampling
  sessionSampleRate: 0.1,        // 10% of all sessions (default: 0.1)
  errorSampleRate: 0.0,          // Error-triggered capture disabled (default: 0.0)

  // Privacy
  unmask: [],                    // CSS selectors to unmask (default: [])

  // Session lifecycle
  idleTimeout: 1800000,          // 30 minutes before session ends (default: 1800000)

  // Transport
  flushInterval: 30000,          // Upload every 30 seconds (default: 30000)
  maxBufferSize: 24117248,       // 23MB max buffer size (default: 24117248)

  // Media
  inlineImages: false,           // Capture images as base64 data URIs (default: false)
  blockMedia: true,              // Block img/video/canvas/svg/iframe (default: true)
});

Options Reference

OptionTypeDefaultDescription
sessionSampleRatenumber0.1Percentage of sessions to record (0.0 to 1.0). Set to 0.1 to record 10% of sessions.
errorSampleRatenumber0.0When an error occurs, capture the last 60 seconds of replay data. Set to 1.0 to capture all error sessions.
unmaskstring[][]CSS selectors for elements to unmask. Only matched elements show real text content; everything else stays masked.
idleTimeoutnumber1800000Milliseconds of inactivity before the session ends. Default is 30 minutes (1,800,000 ms).
flushIntervalnumber30000Milliseconds between automatic uploads of recorded events. Default is 30 seconds (30,000 ms).
maxBufferSizenumber24117248Maximum buffer size in bytes before oldest events are dropped. Default is 23 MB (24,117,248 bytes).
inlineImagesbooleanfalseCapture images as base64 data URIs in the recording. Increases payload size but makes images available during replay.
blockMediabooleantrueReplace media elements (img, video, canvas, svg, iframe) with placeholders. Set to false to capture media element structure.

Option Details

sessionSampleRate

Controls the percentage of all sessions that are recorded from the very start. The sampling decision is made once when the session begins and does not change during the session.

Valid range: 0.0 (no sessions) to 1.0 (all sessions). Values outside this range are clamped with a console warning.

errorSampleRate

When set above 0, the SDK maintains a 60-second ring buffer of replay events. When an error is captured by @tracekit/browser, the buffer contents are flushed and uploaded. This means you get replay data for error sessions even if sessionSampleRate is low.

Valid range: 0.0 (disabled) to 1.0 (all error sessions). The combined sum of sessionSampleRate + errorSampleRate is clamped to 1.0.

unmask

An array of CSS selectors that identify elements whose text content should be visible in the replay. By default, all text is masked (replaced with asterisks). Use this to unmask public-facing content.

Accepts any valid CSS selector: class (.product-title), ID (#hero), tag (h1), or attribute ([data-public]).

idleTimeout

Milliseconds of user inactivity before the session is considered ended. After this timeout, the current recording is flushed and a new session starts on the next user interaction. The default of 30 minutes (1,800,000 ms) matches typical web session definitions.

flushInterval

Milliseconds between automatic uploads of buffered replay events. Lower values reduce the risk of data loss on tab close but increase network requests. The default of 30 seconds (30,000 ms) provides a good balance.

maxBufferSize

Maximum memory in bytes for the replay event buffer before oldest events are evicted. Controls memory usage on the client. The default of 23 MB (24,117,248 bytes) is sufficient for most sessions, including those with inline images enabled. Reduce on memory-constrained devices.

inlineImages

When enabled, images are captured as base64 data URIs and embedded in the recording. This makes images visible during replay but significantly increases payload size. Best combined with a higher maxBufferSize.

blockMedia

When enabled (default), media elements like images, videos, canvases, SVGs, and iframes are replaced with placeholders during recording. Set to false to capture their DOM structure. Combine with inlineImages: true to also capture image content.

Privacy Controls

Session replay is privacy-first by design. All text content, input values, and media are masked by default. You opt in to showing content, not opt out.

Default Behavior

  • M
    All text content is masked. Text nodes are replaced with asterisks of the same length in the recording.
  • M
    All input values are masked. Form fields, textareas, and selects show placeholder content only.
  • B
    Media elements are blocked. Images and videos are replaced with placeholder blocks in the recording.

Unmasking Specific Elements

Use CSS selectors in the unmask config option to reveal specific content that is safe to record.

replayIntegration({
  unmask: [
    '.public-content',    // Marketing copy, product titles
    '.product-title',     // Product names on e-commerce pages
    '.nav-label',         // Navigation menu labels
    'h1', 'h2', 'h3',    // All headings
  ],
})

You can also unmask individual elements with an HTML attribute:

<p data-tracekit-unmask>This text will be visible in the replay.</p>

Always Masked (No Override)

Password fields and credit card inputs are always masked regardless of the unmask configuration or data-tracekit-unmask attribute. These elements cannot be unmasked for security reasons.

Recommended Approach

Start fully masked (the default), then unmask only public-facing content like product names, navigation labels, and headings. Never unmask user-generated content, form inputs, or account-specific data without reviewing your privacy policy.

Sampling

Session replay provides two independent sampling modes. A session can be captured by either mode -- they do not conflict.

Session Sampling

sessionSampleRate controls what percentage of all sessions are recorded from the start. The decision is made when the session begins.

Set to 0.1 for 10% of sessions. Set to 1.0 to record every session.

Error Sampling

errorSampleRate enables a ring buffer that keeps the last 60 seconds of replay data. When an error is captured, the buffer is flushed and uploaded.

Set to 1.0 to capture replay data for every session that encounters an error.

Example Configurations

Production (recommended)

replayIntegration({
  sessionSampleRate: 0.1,   // Record 10% of sessions
  errorSampleRate: 1.0,     // Always capture replay for errors
})

Best balance of coverage and cost. You get a representative sample of all sessions, plus full replay for every error.

Development

replayIntegration({
  sessionSampleRate: 1.0,   // Record every session
  errorSampleRate: 0.0,     // No error-only capture needed
})

Full recording for testing and QA. Not recommended for production due to storage cost.

High-traffic Production

replayIntegration({
  sessionSampleRate: 0.01,  // Record 1% of sessions
  errorSampleRate: 1.0,     // Always capture replay for errors
})

Minimal session recording with full error coverage. Good for high-volume sites where 1% still gives thousands of replays per day.

Sampling Interaction

Session sampling and error sampling are independent. When a new session starts, the SDK makes a sampling decision:

  • Session mode: If the random value is below sessionSampleRate, the session is recorded from the start. All events are uploaded immediately.
  • Buffer mode: If the session was not selected by session sampling but errorSampleRate is above 0, events are buffered in a 60-second ring buffer. If an error occurs, the buffer is flushed and recording continues.
  • Off: If neither mode selects the session, no recording happens.

The combined sum of sessionSampleRate + errorSampleRate is clamped to 1.0. If the sum exceeds 1.0, errorSampleRate is reduced and a console warning is logged.

API Reference

All exports are available from @tracekit/replay.

replayIntegration(config?: ReplayConfig): Integration & { flush(): void; getSessionId(): string }

Create a session replay integration for @tracekit/browser. Pass the returned object in the addons array of init(). The integration starts recording immediately when the SDK initializes.

import { replayIntegration } from '@tracekit/replay';

const replay = replayIntegration({
  sessionSampleRate: 0.1,
  errorSampleRate: 1.0,
});

init({ apiKey: 'your-api-key', addons: [replay] });
flush(): void

Force an immediate upload of pending replay events. Fire-and-forget -- does not return a promise. Useful for ensuring data is captured before a page transition or critical user action.

const replay = replayIntegration();
init({ apiKey: 'your-api-key', addons: [replay] });

// Later, on a critical action:
document.getElementById('checkout')?.addEventListener('click', () => {
  replay.flush();
});
getSessionId(): string | undefined

Get the current replay session ID. Returns an empty string if replay is not active (e.g., the session was not sampled). Useful for custom integrations or logging.

const replay = replayIntegration();
init({ apiKey: 'your-api-key', addons: [replay] });

const sessionId = replay.getSessionId();
if (sessionId) {
  console.log('Replay session:', sessionId);
}

ReplayConfig Type

FieldTypeDescription
sessionSampleRatenumber?Session sample rate from 0.0 to 1.0. Default: 0.1 (10%)
errorSampleRatenumber?Error sample rate from 0.0 to 1.0. Default: 0.0 (disabled)
unmaskstring[]?CSS selectors for elements to unmask. Default: [] (all masked)
idleTimeoutnumber?Idle timeout in milliseconds. Default: 1800000 (30 minutes)
flushIntervalnumber?Upload interval in milliseconds. Default: 30000 (30 seconds)
maxBufferSizenumber?Maximum buffer size in bytes. Default: 10485760 (10 MB)

Playback UI

The replay viewer in the TraceKit dashboard provides a full-featured playback experience for recorded sessions.

Timeline Scrubber

Play, pause, and scrub through the session timeline. Click anywhere on the timeline to jump to a specific moment.

Speed Controls

Adjust playback speed: 0.5x, 1x, 2x, or 4x. Useful for quickly reviewing long sessions or slowing down to catch details.

Inactivity Skipping

Periods of user inactivity are automatically skipped during playback so you only see the moments that matter.

Network Tab

View network requests synchronized with the replay: method, URL, status code, duration, and timing relative to the session.

Console Tab

Console output (log, warn, error) is synchronized with the playback position. Expand objects and view stack traces inline.

Backend Traces

Network requests include "View Backend Trace" links that open the distributed trace waterfall for that specific API call.

Session Card Metadata

Each session card in the replay list shows enriched metadata automatically collected by the SDK and backend:

IndicatorSourceDescription
Country flagCF-IPCountry header / GeoIPEmoji flag derived from the user's IP country code
Browser iconUser-AgentChrome, Firefox, Safari, or Edge icon parsed from User-Agent
Device iconUser-AgentDesktop, mobile, or tablet icon based on User-Agent device detection
OS iconUser-AgentmacOS, Windows, Linux, iOS, or Android icon
Click countSDK (rrweb events)Total mouse clicks recorded during the session
Keypress countSDK (rrweb events)Total input events (keypresses) recorded during the session
Error dotError span linkageRed indicator shown when errors were captured during the session

Country detection uses the CF-IPCountry header when behind Cloudflare, with a MaxMind GeoLite2 fallback for other deployments. If no geo database is available, the country field is simply omitted.

Network Request Details

The network tab in the replay viewer shows all HTTP requests made during the session, synchronized with the playback timeline. Each request displays:

ColumnDescription
MethodHTTP method (GET, POST, PUT, DELETE, etc.)
URLRequest URL (truncated in the list, full URL in detail view)
StatusHTTP status code, color-coded (green for 2xx, yellow for 3xx/4xx, red for 5xx)
DurationRequest duration in milliseconds
TimelineVisual position of the request relative to the replay timeline

When distributed tracing is enabled (via tracePropagationTargets in the browser SDK config), network requests that include a traceparent header show a "View Backend Trace" link. Clicking it opens the distributed trace waterfall for that exact API call, connecting the frontend replay to the backend execution.

Console Output

The console tab displays all console.log, console.warn, and console.error output, synchronized with the replay playback position. Error-level entries include expandable stack traces. Object arguments are displayed as expandable trees.

Error Integration

When both @tracekit/browser and @tracekit/replay are installed, errors and replays are automatically linked. No additional configuration is required.

How It Works

  1. 1
    Error captured. When @tracekit/browser captures an error (via captureException, global error handler, or unhandled rejection), the replay integration intercepts the capture.
  2. 2
    replay_id attached. The current session ID is automatically attached to the error event as a replay_id span attribute (using setTag).
  3. 3
    Ring buffer flushed. If the session is in buffer mode (error sampling), the last 60 seconds of replay data are flushed and uploaded.

"View Replay" Button

On the error detail page in the dashboard, a "View Replay" button appears when a replay_id is present on the error. Clicking it opens the session replay positioned 5 seconds before the error occurred (pre-roll), so you can see exactly what the user was doing leading up to the error.

No additional configuration needed. Just having both @tracekit/browser and @tracekit/replay installed enables the error-replay linkage. The replay_id is stored as a direct OTLP span attribute (not nested under "extra"), making it directly queryable in the dashboard.

Error Sampling Flow

When errorSampleRate is greater than 0, the SDK operates in "buffer" mode for sessions that were not selected by session sampling. In buffer mode:

  • Replay events are captured into a 60-second ring buffer (older events are evicted by timestamp).
  • No data is uploaded until an error occurs.
  • When an error is captured, the ring buffer contents are flushed and uploaded as a compressed chunk.
  • After the flush, the session transitions to full recording mode for the remainder of the session.

This approach gives you full replay context for error sessions with minimal storage cost -- you only pay for sessions where errors actually occurred.

Troubleshooting

Privacy masking too aggressive

By default, all text is masked. To show specific content in replays, add CSS selectors to the unmask config:

replayIntegration({
  unmask: ['.public-content', '.product-title', 'h1', 'h2'],
})

You can also add data-tracekit-unmask to individual HTML elements.

Sampling rate confusion

sessionSampleRate and errorSampleRate are independent. A session can be captured by either mode. If you set sessionSampleRate: 0.1 and errorSampleRate: 1.0, 10% of sessions are recorded normally and 100% of error sessions have at least 60 seconds of replay data.

Large recording sizes

Reduce maxBufferSize to limit memory usage, or lower flushInterval for more frequent uploads of smaller chunks. Replay data is gzip-compressed before upload, so network usage is typically 5-10x smaller than the raw buffer size.

Web Worker compatibility

Replay compression runs in a Web Worker for zero-impact on the main thread. If Web Workers are not available (e.g., in certain sandboxed environments), the SDK falls back to main-thread gzip compression via fflate. Performance impact is minimal in either case.

No replays appearing

Check these common causes:

  • Verify replayIntegration() is passed in the addons array to init().
  • Check that sessionSampleRate or errorSampleRate is greater than 0.
  • Ensure your API key has permissions for replay data ingestion.
  • Look for [TraceKit Replay] warnings in the browser console.
  • Confirm the browser SDK enabled flag is not set to false.

Replay not linked to errors

The replay_id is attached via setTag() when an error is captured. If you are not seeing the "View Replay" button on error detail pages, check that: (1) the replay integration was passed in addons before the error occurred, (2) the session was sampled (either session or error mode), and (3) the replay data was successfully uploaded.

Memory usage on long sessions

For very long sessions (over 30 minutes), the idleTimeout will end and restart the session. If users remain continuously active, reduce maxBufferSize to limit memory usage, or lower flushInterval to upload data more frequently.

Complete Example

A production-ready setup with @tracekit/browser and @tracekit/replay together.

import { init, captureException, setUser } from '@tracekit/browser';
import { replayIntegration } from '@tracekit/replay';

// 1. Create the replay integration with production config
const replay = replayIntegration({
  sessionSampleRate: 0.1,       // Record 10% of sessions
  errorSampleRate: 1.0,         // Always capture replay for errors
  unmask: [
    '.public-content',          // Marketing copy
    '.product-title',           // Product names
    'h1', 'h2', 'h3',          // Headings
    '.nav-label',               // Navigation labels
  ],
});

// 2. Initialize the browser SDK with replay as an addon
init({
  apiKey: process.env.TRACEKIT_API_KEY!,
  release: process.env.APP_VERSION || '0.0.0',
  environment: process.env.NODE_ENV || 'production',
  tracePropagationTargets: [
    'https://api.myapp.com',
    /^https:\/\/.*\.myapp\.com/,
  ],
  addons: [replay],
});

// 3. Set user context after authentication
async function onLogin(email: string, password: string) {
  const user = await api.login(email, password);
  setUser({ id: user.id, email: user.email });
}

// 4. Manual error capture -- replay_id is attached automatically
async function loadDashboard() {
  try {
    const data = await api.fetchDashboard();
    renderDashboard(data);
  } catch (err) {
    captureException(err as Error);
    showErrorFallback();
  }
}

// 5. Force flush on critical user actions
document.getElementById('checkout')?.addEventListener('click', () => {
  replay.flush();
});

// 6. Access the session ID for custom integrations
const sessionId = replay.getSessionId();
if (sessionId) {
  analytics.track('session_started', { replayId: sessionId });
}

Note on tab close: At most 30 seconds of replay data may be lost when a user closes the tab, due to the flush interval. The SDK uses sendBeacon as a fallback on visibilitychange to minimize this window, but some data loss is possible in aggressive browser shutdown scenarios.