Product

How Session Replay Works Under the Hood

Session replay is not screen recording. It captures the structure of a page and every change to it, then rebuilds the page later. Learn how snapshots, mutations, batching, and playback work, and the bugs we hit building ours.

Purushottam Kumar Suman
Purushottam Kumar SumanSeptember 15, 202611 min read
Founder & CEO, DeepSync
Code and DOM tree illustrating how session replay works

Short answer: session replay records the page's DOM as data, not pixels. It takes one full snapshot of the document, then records each later change (nodes added, attributes changed, text edited) along with input events such as clicks and scrolls, each with a timestamp. Playback rebuilds the snapshot inside a sandboxed frame and applies the changes in order. That is why recordings are small, sharp at any size, and searchable.

Table of Contents

  1. Not a Video
  2. Step 1: The Full Snapshot
  3. Step 2: Incremental Events
  4. Step 3: Getting Data Off the Page
  5. Step 4: Storage
  6. Step 5: Playback
  7. Bugs We Hit
  8. Key Takeaways

Not a Video

Video captureDOM-based replay
CapturesPixelsStructure and changes
SizeLargeSmall
SharpnessFixed resolutionAny size, always crisp
PrivacyHard to redact after the factFields masked before capture
SearchableNoYes: every click and element is data
Needs permission promptYes, in browsersNo

DeepSync's SDK is built on rrweb, a widely used open-source library for this approach (SDK docs).

Step 1: The Full Snapshot

When recording starts, the recorder walks the entire document and serialises it: every element, attribute, and text node, plus the styles needed to render it. Each node is given a numeric ID. That ID is the key to everything that follows.

The snapshot is the largest single message in a session. Everything after it is a small description of what changed.

Masking happens here. Text inputs, passwords, and payment fields have their values replaced during serialisation, so the real characters are never part of the data (PII masking).

Step 2: Incremental Events

After the snapshot, the recorder listens.

  • DOM mutations, through the browser's MutationObserver: nodes added or removed, attributes changed, text changed. Each refers to nodes by ID.
  • Mouse movement and clicks, with coordinates and the target node's ID.
  • Scrolling, for the window and scrollable elements.
  • Input changes, with values masked.
  • Viewport resizes.
  • Stylesheet changes, including rules added by scripts.

Every event carries a timestamp. In a single-page app the recorder also notes route changes by patching the History API, so client-side navigation appears as page views.

A mutation event is tiny: "node 482 gained child 913, a div with this class". That is all playback needs, provided it already knows about node 482.

Step 3: Getting Data Off the Page

Sending each event as it occurs would mean hundreds of requests. So events are batched. In DeepSync:

  • Events flush every 500 milliseconds or once 10 are queued, whichever comes first.
  • A full snapshot forces an immediate flush, because replay cannot render without it.
  • On page unload, the final batch goes out with navigator.sendBeacon, which the browser delivers even as the page closes.
  • Flushing pauses while the tab is idle, to save the visitor's bandwidth.

Step 4: Storage

On the server, replay data is chunked and compressed, and kept until the project's retention period ends, after which it is purged and cannot be recovered (session replay docs).

Alongside the raw events, the server derives structured records: clicks, page views, errors, and frustration signals. Those power heatmaps, funnels, and filters. The replay stream is the evidence; the derived records are the index.

Step 5: Playback

Playback reverses the process.

  1. Create a sandboxed frame.
  2. Rebuild the document from the snapshot, restoring each node with its original ID.
  3. Apply incremental events in timestamp order, on a clock.
  4. Draw a cursor and click markers from the mouse events.

Because it is a real document, you can pause and inspect it. Scripts from the original page do not run, which is why a recording is safe to open: nothing in it can submit a form or call an API.

DeepSync's player adds click and scroll markers on the scrubber, a timeline of meaningful events, the console and network timeline, and Highlights that jump to rage clicks, errors, and dead ends.

Bugs We Hit

The model is simple. The edge cases are not. Three that taught us something:

A gap in the stream breaks everything after it

Our SDK pauses recording after a period with no interaction, to avoid sending data for an abandoned tab. While paused, events were discarded. The underlying recorder, however, kept observing the page and assigning IDs to new nodes.

When the visitor returned, recording resumed mid-stream. The next mutation referred to node IDs created during the pause, which the player had never been told about. Playback logged "node not found" and stayed broken for the rest of the session.

The fix was to take a fresh full snapshot on resume. A new snapshot re-establishes every ID, so nothing after it depends on the missing stretch. The general rule: an incremental stream cannot tolerate holes. After any gap, start again from a snapshot.

Entrance animations left pages blank

Some recordings showed only a background, with the navigation and content missing. The capture was complete. The cause was CSS entrance animations: elements start at zero opacity and animate in. The recorder captured the starting state, and the player pauses animations so it can control time, which left those elements invisible forever.

The fix was a style rule injected at playback that forces such elements to their finished state.

Images came out the wrong size

Inlining images into the recording dropped the width and height attributes on optimised image elements, so they replayed at the bitmap's natural proportions. We switched to referencing images at their original address, which preserves the attributes. The trade-off is that an image must still exist at that address when the recording is watched.

What This Design Cannot Do

Because replay reconstructs the DOM, content outside the DOM is difficult: canvas drawing, cross-origin frames, and closed shadow roots. We cover those in why session replay struggles with iframes, canvas, and shadow DOM.

Key Takeaways

  • Replay records structure and changes, not pixels.
  • A full snapshot assigns IDs; later events refer to them.
  • Events are batched and compressed.
  • Any gap in the stream must be followed by a new snapshot.

Conclusion

Session replay works because a web page is already a structured document. Record the structure and its changes faithfully, and you can rebuild any moment of a visit.

Read how the DeepSync SDK works.

Frequently Asked Questions

Was this article helpful?

Ready to understand
users like never before?

Join thousands of teams who use DeepSync to uncover insights,improve experiences, and build better products—faster.

Quick & easy onboarding
See results in real time
Enterprise-grade security