Skip to content

Framework integration

Session Replay for React Apps: Setup and Tips

Record a React app with session replay: install the browser SDK, create the recorder once, keep routes and Strict Mode working, and track events from handlers.

Updated · 4 min read

React session replay takes one dependency and one module: install @anyreplay/browser, create the recorder in its own file, and import that file from your entry point before the first render. React Router and other History API routers are recorded automatically, and you can add named events from your handlers.

This page covers a client-rendered React app built with Vite. If you use Next.js, see session replay for Next.js instead, since server rendering changes where the recorder starts.

Why add session replay to a React app

React bugs are often about state: a component that re-renders with stale props, a modal that opens twice, a submit button disabled by a condition nobody expected. Error trackers tell you something threw; they rarely show the ten clicks before it. A session recording replays the DOM as the user saw it, with clicks, rage clicks and route changes on a timeline, so a QA engineer or support agent can hand a developer the exact sequence instead of a paragraph describing it. For turning those recordings into fixes, see how to fix rage clicks.

How to install session replay in a React app

npm install @anyreplay/browser
import { createRecorder } from '@anyreplay/browser';

export const recorder = createRecorder({
  projectKey: import.meta.env.VITE_ANYREPLAY_KEY,
});
src/anyreplay.ts
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './anyreplay'; // start recording before the first render
import App from './App';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
);
src/main.tsx

Put VITE_ANYREPLAY_KEY=YOUR_PROJECT_KEY in .env. The key is public, so it is safe in the client bundle. The package includes TypeScript types and bundles its recording engine, so there is nothing else to install.

Why create the recorder in a module, not a component?

createRecorder does not guard against being called twice: two calls start two recorders. An ES module is evaluated once per page load, however many components import it. A useEffect is not: Strict Mode runs effects twice in development, and a component can unmount and mount again. Keep the call at module level and import the exported recorder wherever you need it.

createRecorder validates options when it is called, so a malformed key or an out-of-range sampleRate throws a ConfigError straight away in development. After that, nothing the recorder does throws into your app.

Are React Router route changes recorded?

Yes. React Router, TanStack Router and other routers that use the History API need no integration. The recorder detects pushState, replaceState and back or forward navigation, and each real URL change becomes a page view with a $navigate marker in the player. Modals, tabs and wizard steps that do not change the URL are not page views; mark them with events if you want them on the timeline. See single-page apps.

How do I track events and identify users?

import { recorder } from './anyreplay';

<button onClick={() => recorder.track('checkout_started', { items })}>Checkout</button>

// after sign-in succeeds
recorder.identify({ userId: user.id });

Call track from event handlers rather than from render, so an event means something happened. It never throws. Names are 1 to 64 characters of letters, digits and _.:-; properties are a plain JSON object up to 4 KB and are not masked, so keep personal data out of them. Events show on the timeline, and the session list can filter on them with Has event. Call identify once sign-in succeeds, and put recorder.sessionId() in your error reports to jump from a bug to its recording. See custom events.

How do I keep sensitive data out of recordings?

  • What people type into inputs is masked by default. Pass maskAllInputs: false to record field values, or maskAllTyping: true to mask rich-text editors as well. Passwords, one-time codes and payment fields are masked in every configuration; type="email" and type="tel" inputs are ordinary fields, recorded only if you opt out.
  • Add className="ar-mask" to components that render personal data, such as a profile card, or to a single input to mask its value.
  • Add className="ar-block" to anything that should not be recorded at all.
  • For a consent banner, pass requireConsent: true and call recorder.consent(true) when the visitor agrees, on every page load for visitors who already did.

How do I check it is working?

Run the app, click through a couple of routes, and open your project's setup page: it shows Connected after the first accepted event. The network panel should show POST requests to in.anyreplay.com answered with 202. If nothing arrives, add debug: true to the options and read the console. Then follow the React install guide and the npm package reference for every option.

Frequently asked questions

Does it work with Create React App or webpack instead of Vite?

Yes. The package ships ES module and CommonJS builds, so any bundler works. Only the way you read the environment variable changes.

Why do I see two sessions for one page in development?

Most likely createRecorder is being called inside a component or effect, and Strict Mode runs it twice. Move the call into its own module and import that module once.

Do I need a React Router integration?

No. Route changes made through the History API are recorded automatically as page views with a marker on the timeline.

Does it record React Native apps too?

React Native uses a separate package, @anyreplay/react-native, which records the element tree instead of the DOM. Sessions from both appear in the same session list.

Start free — 1 project, 500 sessions a month

Install the snippet, record a session, and watch it back in your own language.