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/replayimport { 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
- 1Recording 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. - 2Events 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).
- 3Chunks are uploaded periodically. Every 30 seconds (configurable via
flushInterval), compressed chunks are uploaded to TraceKit. On tab close,sendBeaconis used as a last-resort upload. - 4Playback 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
| Option | Type | Default | Description |
|---|---|---|---|
| sessionSampleRate | number | 0.1 | Percentage of sessions to record (0.0 to 1.0). Set to 0.1 to record 10% of sessions. |
| errorSampleRate | number | 0.0 | When an error occurs, capture the last 60 seconds of replay data. Set to 1.0 to capture all error sessions. |
| unmask | string[] | [] | CSS selectors for elements to unmask. Only matched elements show real text content; everything else stays masked. |
| idleTimeout | number | 1800000 | Milliseconds of inactivity before the session ends. Default is 30 minutes (1,800,000 ms). |
| flushInterval | number | 30000 | Milliseconds between automatic uploads of recorded events. Default is 30 seconds (30,000 ms). |
| maxBufferSize | number | 24117248 | Maximum buffer size in bytes before oldest events are dropped. Default is 23 MB (24,117,248 bytes). |
| inlineImages | boolean | false | Capture images as base64 data URIs in the recording. Increases payload size but makes images available during replay. |
| blockMedia | boolean | true | Replace 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
- MAll text content is masked. Text nodes are replaced with asterisks of the same length in the recording.
- MAll input values are masked. Form fields, textareas, and selects show placeholder content only.
- BMedia 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
errorSampleRateis 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(): voidForce 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 | undefinedGet 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
| Field | Type | Description |
|---|---|---|
| sessionSampleRate | number? | Session sample rate from 0.0 to 1.0. Default: 0.1 (10%) |
| errorSampleRate | number? | Error sample rate from 0.0 to 1.0. Default: 0.0 (disabled) |
| unmask | string[]? | CSS selectors for elements to unmask. Default: [] (all masked) |
| idleTimeout | number? | Idle timeout in milliseconds. Default: 1800000 (30 minutes) |
| flushInterval | number? | Upload interval in milliseconds. Default: 30000 (30 seconds) |
| maxBufferSize | number? | 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:
| Indicator | Source | Description |
|---|---|---|
| Country flag | CF-IPCountry header / GeoIP | Emoji flag derived from the user's IP country code |
| Browser icon | User-Agent | Chrome, Firefox, Safari, or Edge icon parsed from User-Agent |
| Device icon | User-Agent | Desktop, mobile, or tablet icon based on User-Agent device detection |
| OS icon | User-Agent | macOS, Windows, Linux, iOS, or Android icon |
| Click count | SDK (rrweb events) | Total mouse clicks recorded during the session |
| Keypress count | SDK (rrweb events) | Total input events (keypresses) recorded during the session |
| Error dot | Error span linkage | Red 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:
| Column | Description |
|---|---|
| Method | HTTP method (GET, POST, PUT, DELETE, etc.) |
| URL | Request URL (truncated in the list, full URL in detail view) |
| Status | HTTP status code, color-coded (green for 2xx, yellow for 3xx/4xx, red for 5xx) |
| Duration | Request duration in milliseconds |
| Timeline | Visual 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
- 1Error captured. When @tracekit/browser captures an error (via
captureException, global error handler, or unhandled rejection), the replay integration intercepts the capture. - 2replay_id attached. The current session ID is automatically attached to the error event as a
replay_idspan attribute (usingsetTag). - 3Ring 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 theaddonsarray toinit(). - Check that
sessionSampleRateorerrorSampleRateis 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
enabledflag is not set tofalse.
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.