Core Web Vitals

Session Replay and Analytics SDKs: The INP Tax

Sentry Replay, Hotjar, FullStory and heavy analytics SDKs record every interaction, which costs every interaction. How to measure their share and defer them.

6 min read
Core Web VitalsThird PartiesINP
1,055 words6 min read

What replay actually does

A session replay tool records the DOM, every mutation to it, every mouse move, scroll and click, serialises them and ships them to a server so someone can watch the session later. To do that it hooks MutationObserver on the whole document, wraps event listeners, and periodically snapshots. All of that runs on the main thread, in the same frames as the user's interactions.

The result is a tax on every interaction: when the user taps a menu, the replay tool observes the DOM change, serialises it and queues it, inside the interaction's frame. On a laptop this is a few milliseconds. On a mid-range phone it can be fifty or more, added to every tap, and it lands directly in Interaction to Next Paint.

Heavy analytics SDKs (full-featured product analytics, marketing suites, A/B testing tools that rewrite the DOM) impose a similar cost through different means: large bundles to parse, autocapture wrapping every event, and synchronous DOM rewrites before first paint.

Measuring the share

Do not guess. In DevTools Performance on a throttled mobile profile, record an interaction and open the bottom-up view for the interaction's frames, grouped by script URL. The replay and analytics scripts show as their own rows with self time. Alternatively, the Long Animation Frames API in field attribution names the script with the most self-time in a slow interaction; if the replay SDK is at the top of that list across many interactions, you have your answer.

Lighthouse's third-party summary shows main-thread time per origin for load, which is useful for the initial cost but misses the per-interaction tax.

Typical findings on business sites: a replay SDK responsible for 20 to 40 percent of main-thread time during interactions, and an autocapture analytics SDK adding 10 to 20 percent. Removing or deferring them is often the largest single INP improvement available.

The options, in order of how much INP they recover

1. Remove it

Ask who watches the replays. On many small business sites the honest answer is nobody, or someone did for a month a year ago. A tool nobody uses is pure cost. Remove it, and the INP improvement is immediate.

2. Sample

Replay tools support sampling: record 5 or 10 percent of sessions instead of all. The visitors not sampled pay nothing. For most purposes (spotting UX problems, debugging reported issues) a sample is as useful as full capture. Sentry Replay's replaysSessionSampleRate and error-triggered replaysOnErrorSampleRate let you record almost nothing by default and capture the session only when an error occurs, which is the case that matters.

3. Load late

Initialise the SDK after the page is interactive and after the first interaction, not on load. In Next.js, next/script with strategy="lazyOnload", or a manual requestIdleCallback with a timeout, or an explicit "load after first pointerdown". The first interactions, which are the ones most likely to be slow because they overlap with hydration, happen before the SDK exists. You lose the first second of the recording, which is rarely the interesting part.

4. Exclude the interaction-heavy pages

Replay on the checkout, the booking form and the search page, where interactions are dense and INP matters most, costs the most. Configure the SDK to skip those routes, or to start recording only after the user has been idle.

5. Reduce what it captures

Most SDKs let you turn off mouse-move capture, reduce mutation batching frequency, mask or block large subtrees, and disable canvas and iframe recording. Each reduces per-interaction work. Block the sections that change most (a live chat panel, an animated hero) so their mutations are not serialised.

6. Move it off the main thread

Some SDKs support a worker-based mode for compression and network; Sentry Replay compresses in a worker by default. Partytown can run some analytics SDKs in a worker entirely, at the cost of features that need synchronous DOM access. Worth testing; not a universal fix, because observing the DOM has to happen on the main thread.

Analytics SDKs specifically

For product analytics and marketing tags:

  • Turn off autocapture. Autocapture wraps every click, input and page change with handlers. Explicit events for the ten things you care about cost a fraction.
  • Load through the tag manager with triggers, not on page load: after a timer, after scroll, after consent.
  • One analytics tool, not three. GA4 plus a product analytics tool plus a marketing suite is three SDKs doing overlapping work.
  • A/B testing tools that rewrite the DOM synchronously (anti-flicker snippets that hide the page until the experiment loads) are the worst case for both LCP and INP. Server-side experimentation, or edge-side, removes the client cost entirely.

Loading late, concretely

TSX
// components/LateSdks.tsx
'use client'
import { useEffect } from 'react'

export function LateSdks() {
  useEffect(() => {
    let started = false
    const start = () => {
      if (started) return
      started = true
      import('@/lib/replay').then((m) => m.init({ sampleRate: 0.1 }))
    }
    // after first interaction, or when idle, whichever first, but never before load
    const onFirstInput = () => { start(); cleanup() }
    const idle = 'requestIdleCallback' in window
      ? window.requestIdleCallback(start, { timeout: 8000 })
      : window.setTimeout(start, 4000)
    window.addEventListener('pointerdown', onFirstInput, { once: true, passive: true })
    window.addEventListener('keydown', onFirstInput, { once: true })
    const cleanup = () => {
      window.removeEventListener('pointerdown', onFirstInput)
      window.removeEventListener('keydown', onFirstInput)
    }
    return () => {
      cleanup()
      if ('cancelIdleCallback' in window) window.cancelIdleCallback(idle as number)
      else clearTimeout(idle as number)
    }
  }, [])
  return null
}

The SDK's code is not even downloaded until the page is idle or the user has interacted once. Its first cost lands after the interaction that would have been slowest.

Verifying

Field INP p75 for the affected page groups over two weeks, and the Long Animation Frames attribution: the SDK should drop out of the top-scripts list. In the lab, the same interaction recorded before and after shows the SDK's self time gone from the interaction's frames.

Where this sits

Replay and heavy analytics are the first thing we look at in a Core Web Vitals audit when INP is failing and the site's own code is reasonable, because the fix is configuration rather than engineering, and because the conversation with the business ("who watches these?") often ends with the tool removed and the metric fixed in an afternoon.

All writingHire me for this