Skip to content

React Native session replay

Mobile replay that records words, not pixels

Every other React Native replay tool captures the screen as images, because JavaScript cannot reach the native view tree without a native module. AnyReplay reads the layer JavaScript can reach — the element tree React has committed — and records a wireframe of structure and text. Small, honest, and translatable.

0
native modules
2
lines of app code
a few KB
first frame of a screen
sends nothing
idle screen
A phone-shaped glass slab dissolving into a lattice of glowing wireframe rectangles and nodes

Why screenshots were the wrong starting point

Screenshot recorders on React Native capture two or more images a second and upload them. Published figures from other vendors put that between 50 KB and 500 KB per minute at the low end and about 0.5 MB per minute at the high end. That is a cost on the user’s battery and data plan before it is a cost on your bill. It is also a dead end for the one thing AnyReplay exists to do: a picture of a Turkish checkout screen cannot be turned into an English one. Pixels have no words in them.

A tool whose reason to exist is showing a session in a language the reviewer reads cannot throw the words away on the way in. So the mobile SDK never takes a screenshot at all.

What the recorder actually captures

On each tick the SDK walks the element tree React has committed — the same tree React DevTools reads — and notes, for each host element, what it is and what it says: a Text with its string, a Pressable with its label, a TextInput with its placeholder. It then measures where each element sits on screen, which needs React Native’s New Architecture. React knows what is on screen; the measurement knows where it is; together they describe the screen well enough to replay it.

The result is a wireframe, not a photograph. Boxes in the right places with the right text in them, updated when the tree changes. That is honest about what was captured, and it is exactly what makes a session watchable in another language: every string is still a string when it reaches the player.

  • The first frame of a screen is a few kilobytes.
  • Every frame after it carries only what changed — often a handful of numbers.
  • An idle screen sends nothing at all.
  • A tap is recorded when the finger lifts, so a scroll or a swipe is never mistaken for one, and scroll positions are recorded too.
  • Navigation is tracked through your NavigationContainer, so screens have names in the timeline.

Two lines of app code, no rebuild

There is no native module, which means no pod install, no Gradle change, and no rebuild — it runs in Expo Go on SDK 52 or later. The second line wraps your app in <AnyReplay>, which is how the recorder finds React’s tree and hears taps, since React Native has no document to listen on; everything else the recorder takes for itself.

App.tsx
npm install @anyreplay/react-native

// App.tsx
import { AnyReplay, init, trackNavigation } from '@anyreplay/react-native';

init({
  projectKey: 'ar_pk_live_your_project_key',
  ingestUrl: 'https://in.anyreplay.com',
  // Your bundle id (iOS) or package name (Android). Required once the project lists allowed domains or apps.
  // appId: 'com.example.app',
  // Stop recording after this many sessions in a calendar month.
  // maxSessionsPerMonth: 10000,
  // Fields are masked: what people type replays as asterisks. Secure
  // fields, passwords, one-time codes and card fields are masked either
  // way. Uncomment the first line to record what people type, or the
  // second to mask every field in a way nothing can weaken.
  // maskAllInputs: false,
  // maskAllTyping: true
});

export default function Root() {
  return (
    <AnyReplay>
      <NavigationContainer ref={(ref) => { if (ref) trackNavigation(ref); }}>
        <App />
      </NavigationContainer>
    </AnyReplay>
  );
}

Options cover what a mobile team needs: sampleRate is decided once per visitor so a recorded person stays recorded, snapshotIntervalMs is the main cost lever, requireConsent records nothing until you call consent(true), and passing AsyncStorage lets the SDK recognise a returning user. The full API is ten functions — init, stop, consent, identify, screen, track, trackError, trackNavigation, flush and getSessionId — and the <AnyReplay> wrapper. If your project lists allowed domains, add the app’s bundle id under Allowed apps and pass the same value as appId; every session then shows which platform and app version recorded it.

Masked on the device, before anything leaves it

Masking is applied on the device, before anything is sent, and it works exactly as it does in the browser. Anything with secureTextEntry is masked whatever the component calls itself; so is any field a textContentType, an autoComplete, a keyboardType, a placeholder or a label marks as a password, a one-time code or a payment field; and so is any value of 13 to 19 digits that passes the card-number check, along with every digit after the sixth of a card number still being typed. Anything whose testID contains ar-mask is masked because you said so. What people type into other fields is masked too, unless you set maskAllInputs: false. A masked element keeps its box and loses its words: the reviewer sees that something was typed, and where, and never what. The value is replaced before it is sent — it is not uploaded and then hidden. One limit is worth stating plainly: after that opt-out, a bare <TextInput /> with no props says nothing about itself and is recorded, exactly as an <input> with no attributes is on the web. The privacy page covers both SDKs.

What it does not do yet

We would rather you knew the edges before you installed. A FlatList reports the rows it has rendered, so scrolling back through a replay shows the rows that existed at that moment, not the whole list. Images that ship in your app bundle are uploaded once and drawn; photos from the device are never read, and maskImages: true records no images at all. Custom native components are recorded as an unknown box in the right place with no text, because there is nothing readable to capture. Maps, web views and videos are labelled boxes whose contents are not captured, and the on-screen keyboard is not drawn. There is no native iOS or Android SDK yet.

Same dashboard, same player, same sandbox

Mobile sessions sit next to web sessions in the same project list, replay in the same player with the same timeline and interaction list, and go through the same translation pipeline. They are recorded as data rather than markup, so the replay sandbox has even less to worry about — there is no DOM from someone else’s app to render at all. Read the glossary for the terms used here, or the blog for the engineering behind the recorder.

Watch any session in your language.

Install in minutes, keep the Free plan for as long as you like. One project and 500 sessions a month, free.