CloseTrace

Open source

rrweb

Definition

rrweb (record and replay the web) is an open-source MIT-licensed JavaScript library that records DOM mutations, user inputs, and network metadata as a serialized event stream and replays them inside a sandboxed iframe. Created by Yuyz0112, it powers the session replay features of PostHog, Sentry, OpenReplay, Highlight, and CloseTrace.

Also called: record-and-replay-the-web

What is rrweb in one paragraph?

rrweb is the de-facto open-source recorder for web session replay. It is a small JavaScript library (~50 KB gzipped for the recorder) that takes an initial DOM snapshot, then streams every change to that DOM as a list of typed events. A separate rrweb-player package consumes those events and rebuilds the original page inside an iframe so the recording can be scrubbed like a video — except it is a real DOM, not pixels.

Who created it and what is the license?

  • Author: Yuyz0112 (Yanzhen Yu)
  • First public release: 2018
  • License: MIT
  • Repository: github.com/rrweb-io/rrweb
  • Written in: TypeScript

Because it is MIT-licensed, commercial vendors can embed it without restriction, which is why most of the session replay industry runs on top of it.

How does the recorder work at a high level?

The recorder has three responsibilities:

  • Take a serializable snapshot of the current DOM, including computed styles for inline elements and the contents of stylesheets
  • Subscribe to every source of change (DOM, input, mouse, scroll, viewport, network)
  • Emit each change as a typed event onto a callback the host application provides

Sources of change are wired up via standard browser APIs:

  • MutationObserver for DOM tree changes
  • addEventListener for input, click, scroll, mousemove, touchstart, selectionchange
  • A monkey-patched CSSStyleSheet.prototype.insertRule for stylesheet edits
  • Wrappers around XMLHttpRequest and fetch for network metadata
  • MediaQueryList and ResizeObserver for viewport changes

How do you start recording in code?

The minimal example from the rrweb README:

import * as rrweb from "rrweb";

const events = [];

const stopFn = rrweb.record({
  emit(event) {
    events.push(event);
  },
  maskAllInputs: true,
  maskInputOptions: { password: true },
  blockClass: "rr-block",
  ignoreClass: "rr-ignore",
});

// Later, ship `events` to your backend
// stopFn() to stop recording

The emit callback fires every time a new event is produced. In production you batch events, gzip them, and send them to your backend on a timer or on visibilitychange.

What event types does rrweb produce?

TypeDescription
0DomContentLoaded
1Load
2FullSnapshot — the initial serialized DOM tree
3IncrementalSnapshot — a delta (mutation, input, scroll, mouse, etc.)
4Meta — viewport size, URL
5Custom — user-defined events
6Plugin — events from rrweb plugins

Type 3 is by far the most frequent and contains its own sub-source enum (Mutation, MouseInteraction, Scroll, Input, MouseMove, etc.).

How does the replayer work?

rrweb-player (or rrweb.Replayer directly) takes the array of events and:

  1. Creates a sandboxed iframe with sandbox="allow-same-origin"
  2. Rebuilds the FullSnapshot inside that iframe by setting innerHTML and re-attaching stylesheets
  3. Walks forward through IncrementalSnapshots, applying each one in time order
  4. Renders a virtual cursor and visual click pulses on top
  5. Exposes play, pause, goto(timestamp), and a timeline scrubber

Because it is a real DOM, you can right-click and inspect a replay in DevTools just like a live page.

Which products are built on rrweb?

  • PostHog Session Replay
  • Sentry Session Replay (forked variant)
  • OpenReplay
  • Highlight.io
  • CloseTrace
  • Many internal observability stacks at companies that built their own player

This makes rrweb's event format a de-facto standard — replays can often be moved between tools.

What about privacy?

rrweb ships with built-in privacy primitives:

  • maskAllInputs masks every input value
  • maskInputOptions lets you enable masking per input type
  • blockClass / blockSelector skips entire subtrees
  • ignoreClass skips capturing inputs on matching elements
  • A maskTextFn callback for custom redaction (PII, credit cards, etc.)
  • Password fields are masked unconditionally

How it relates to CloseTrace

CloseTrace uses rrweb under the hood for its session capture pipeline, with PII masking enabled by default and custom event types layered on top to mark form steps, rage clicks, and dead clicks for the lead recovery inbox.