Setup How-Tos 2026-07-18

Adding Session Replay to a Vite + React App

Setting up session replay in a Vite + React app takes about ten minutes, and nearly all of the questions are about placement: where the init call goes, how to keep it out of development, and how to stop React strict mode from initializing your recorder twice. This guide walks the session replay setup for Vite and React specifically, because that combination has a couple of quirks the generic SDK docs gloss over.

The examples use LogReplay’s SDK shape, but every rrweb-based recorder — Highlight, OpenReplay, PostHog — has the same three moving parts: an init call, an identify call, and environment config. Translate accordingly.

Where the init call goes

Initialize before React renders, in your entry file — not inside a component.

// src/main.tsx
import { LogReplay } from '@logreplay/browser'
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

if (import.meta.env.PROD) {
  LogReplay.init(import.meta.env.VITE_LOGREPLAY_PROJECT_ID, {
    environment: import.meta.env.MODE,
    networkRecording: {
      enabled: true,
      recordHeadersAndBody: true,
    },
  })
}

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

Two reasons this beats initializing inside a component or effect. First, module scope runs exactly once per page load, so React strict mode’s double-invoking of effects can’t double-init the recorder. If you’ve seen duplicated sessions or doubled event volume in development, an init call inside useEffect is almost always why. Second, initializing early means the recorder catches the first render and any errors during mount — starting it from a deep component misses the most failure-prone seconds of the session.

If you must init from a component (say, after a consent check), guard it:

let started = false

function startReplayOnce() {
  if (started) return
  started = true
  LogReplay.init(/* ... */)
}

Environment variables the Vite way

Vite only exposes variables prefixed with VITE_ to client code, and it inlines them at build time. Put the project ID in .env.production:

# .env.production
VITE_LOGREPLAY_PROJECT_ID=your-project-id

Because the value is baked into the bundle at build time, changing it means rebuilding — there’s no runtime lookup. That’s fine for a project ID (it’s not a secret; it ships to every browser anyway), but it trips people up when staging and production share one build artifact. If that’s your deploy model, read the ID from a global injected by your host page instead of import.meta.env.

The import.meta.env.PROD guard above keeps development sessions out of your dashboard. I’d actually soften that advice slightly: record staging too, under a separate environment tag. Staging replays are how you verify masking rules before real user data is at stake, and testing your replay setup before production is one of those chores that pays for itself the first week.

Identifying users after login

Anonymous sessions are fine for public pages, but the moment a user signs in you want the session searchable by who they are:

// wherever your auth state resolves
LogReplay.identify(user.email, {
  id: user.id,
  plan: user.plan,
})

Call it once per session after auth resolves — an effect watching your auth context is the natural place, and unlike init, identify is safe to call more than once. Send attributes you’ll actually filter by (plan, role, workspace). Don’t send tokens, and think twice before sending anything you wouldn’t want in a support engineer’s search results.

Source maps, since you’re here

Vite hides a small gift behind one config line:

// vite.config.ts
export default defineConfig({
  build: {
    sourcemap: true,
  },
})

With source maps generated, the errors your replay tool captures resolve to real file names and line numbers instead of minified soup. Whether you serve the maps publicly, upload them to your monitoring tool, or generate-then-delete is a separate decision with real tradeoffs — but generate them. A replay showing what the user did, paired with a stack trace that points at which line reacted badly, is the whole point of wiring these tools together.

The checklist

That last item is not optional. Every replay setup I’ve reviewed had at least one surprise in the first recording — a masked field that wasn’t, a third-party widget recording more than expected, or a network body that should have been redacted. Ten minutes of watching your own session is the cheapest privacy audit you’ll ever run.

See the bug the way your user did

LogReplay captures session replays, console output, network requests, and errors in one timeline — so you stop guessing what happened before the ticket arrived.

Try LogReplay free