• /
  • EnglishEspañolFrançais日本語한국어Português
  • EntrarComeçar agora

Optimize session replay data volume

|View as Markdown (English)

Session replay payload size doesn't just depend on how many sessions you record — it also depends on the complexity of your site's DOM and how often that DOM changes. This guide explains why, and gives you concrete ways to reduce the amount of data a replay generates. For the pricing formula and average bytes-per-replay estimate, see Data consumption.

Why DOM complexity drives payload size

Session replay doesn't record video or screenshots. It captures your page's DOM structure, then tracks and reports the changes (mutations) made to it as compressed payloads, as described in Session replay and your app's performance.

There are two independent cost drivers behind that payload size:

  • Snapshot size: how many DOM nodes exist, and how deeply they're nested, at the moment a full snapshot is captured.
  • Mutation volume: how often those nodes' attributes, classes, or text change afterward.

Adjusting your sampling rate controls how many sessions get recorded. The techniques in this guide control how expensive each recorded session is, regardless of your sampling rate.

Common patterns that inflate payload size

The following patterns are common on content-heavy pages, such as a product page with a star-rating widget built from inline icons and an auto-rotating image carousel.

Inline SVGs and icon-heavy widgets

Repeated inline SVG markup — for example, a rating widget that draws each star as its own <svg>, with nested <path>, <defs>, and gradient elements — multiplies your DOM node count quickly. Five stars drawn this way can easily add dozens of nodes; a page full of rating widgets can add thousands.

<!-- Avoid: each star repeats a full, independent SVG definition -->
<div class="rating">
<svg viewBox="0 0 20 20">
<defs><linearGradient id="star-fill-1">...</linearGradient></defs>
<path d="..." />
</svg>
<svg viewBox="0 0 20 20">
<defs><linearGradient id="star-fill-2">...</linearGradient></defs>
<path d="..." />
</svg>
<!-- repeated for every star, on every rating widget on the page -->
</div>

To reduce this:

  • Use a CSS background-image or sprite for simple, static icons instead of inline SVG. This removes the icon's markup from the DOM entirely.

  • If inline SVG is required, define the shape once and reuse it: put the shape in a hidden <symbol>, then reference it with <use> everywhere you need it.

    <svg style="display: none;">
    <symbol id="star-icon" viewBox="0 0 20 20"><path d="..." /></symbol>
    </svg>
    <div class="rating">
    <svg><use href="#star-icon"></use></svg>
    <svg><use href="#star-icon"></use></svg>
    </div>
  • If the widget doesn't need visual fidelity in replay, exclude it from capture using either the nr-block CSS class (or data-nr-block attribute) on the widget's container, or the Block selectors field in Application settings if you'd rather not touch your markup (useful for third-party widgets you can't edit). See Block site content.

    Importante

    Don't apply nr-block, nr-ignore, or a block selector to the container that holds your shared <symbol> definitions, even though that container is usually hidden and looks safe to exclude. Blocking a container removes its children from the captured DOM entirely — which would break every <use> reference to those symbols anywhere else on the page. Only block or ignore the individual visual instances (the <use> elements), never the shared definitions.

Bloated initial snapshot

The first full snapshot of your page captures everything present in the DOM at that moment, including content that isn't visible or isn't needed for replay:

  • A large, minified <style> block inlined directly in the page is captured as text in the snapshot.
  • Elements that are immediately hidden (display: none or visibility: hidden) are still captured, even though they contribute nothing to what a viewer sees in the replay.

To reduce this:

  • Externalize CSS using <link rel="stylesheet"> instead of a large inline <style> block. Session replay captures the link, not the file's contents, which keeps the snapshot small. If your CSS is hosted on a different domain, you'll need the crossorigin="anonymous" attribute for it to be captured correctly — see Manage cross-origin CSS for session replays.
  • Lazy-load below-the-fold content, such as modals or secondary sections, instead of rendering everything at page load.
  • Block or ignore elements that are immediately hidden and only appear after a user action, using nr-block/nr-ignore or Block selectors as described above.

High-frequency attribute and class mutations

Some UI patterns generate a steady stream of mutation events for as long as a session lasts. A common example is an auto-rotating image carousel that swaps a CSS class every few seconds to change the visible image — each swap is a mutation event, repeated for the entire length of the session.

To reduce this:

  • Throttle or debounce DOM updates that are driven by animation, scrolling, or auto-rotation, where your UX allows it.
  • Prefer a single class toggle driven by a CSS transition or animation over JavaScript that repeatedly writes to style or class attributes.
  • Block purely decorative, high-churn elements — spinners, auto-rotating carousels, marquee-style banners — with nr-block or Block selectors if you don't need them reflected in replay.

Audit your site for size-inflating patterns

Before making changes, it helps to confirm where your own payload size is actually coming from.

  1. Open your browser's DevTools and inspect the DOM node count and nesting depth on a representative page. The Elements panel, or a quick document.querySelectorAll('*').length in the console, gives you a rough count.
  2. Look for repeated or deeply nested inline SVG markup, and large inline <style> blocks in the page source.
  3. Watch the Elements panel while interacting with the page normally, and note any elements whose class or attributes change every few seconds — these are your highest-volume mutation sources.
  4. Cross-reference what you find against candidates for nr-block, nr-ignore, or Block selectors.

You can also compare snapshot-size bytes against mutation-volume bytes directly with a NRQL query:

SELECT sum(newrelic.timeslice.value)
FROM Metric
WHERE (metricTimesliceName LIKE 'Browser/Supportability/rrweb/node/%/bytes') AND (`entity.guid` = 'YOUR_ENTITY_GUID')
FACET metricTimesliceName
SINCE 30 minutes ago UNTIL now
TIMESERIES

Replace YOUR_ENTITY_GUID with your browser app's entity GUID. The .../node/2/bytes series reflects full-snapshot (DOM complexity) cost, and the .../node/3/bytes series reflects mutation (DOM churn) cost. Run this before and after applying a fix to confirm it actually reduced your payload size.

Best practices checklist

  • Use CSS sprites, background images, or a <symbol>/<use> pattern instead of duplicating inline SVG markup.
  • Externalize large inline CSS blocks.
  • Lazy-load below-the-fold content instead of rendering everything up front.
  • Block decorative or high-churn elements with nr-block or Block selectors — but never the container holding shared <symbol> definitions.
  • Ignore high-frequency input fields where the value itself doesn't matter for replay, using nr-ignore.
  • Throttle or debounce animation- and scroll-driven DOM updates.
  • Measure before and after any change with a short test period, comparing snapshot-size and mutation-volume bytes.
  • Treat sampling rate as a complementary lever, not a substitute — it controls how many sessions are recorded, not how much each one costs.
Copyright © 2026 New Relic Inc.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.