OpenReplay is the open-source, self-hostable session replay tool — import Tracker from "@openreplay/tracker", const tracker = new Tracker({ projectKey, ingestPoint: "https://your-host/ingest" }) initializes. tracker.start() begins recording — call after consent if required. tracker.setUserID(userId) associates the session with a user. tracker.setMetadata("plan", "pro") adds searchable attributes. tracker.event("checkout_started", { cartValue: 49 }) fires a custom event visible in the session timeline. Plugins: tracker.use(trackerNetwork({ failuresOnly: false })) records XHR/fetch requests. tracker.use(trackerRedux({ store })) shows Redux state diffs. tracker.use(trackerPerf()) captures Core Web Vitals. tracker.use(trackerAssist()) enables co-browsing for live support. Privacy: obscureTextEmails: true, obscureInputDates: true, defaultInputMode: 1 (obscure all inputs by default), blockClass: "or-hidden" CSS class hides elements. tracker.stop() ends recording. Issue recording: call tracker.issue("Bug title", { severity: "critical" }) to flag problems. Self-hosting: deploy with Docker Compose or Kubernetes — OpenReplay Community Edition is fully open-source. Claude Code generates OpenReplay tracker setup, plugin configuration, and privacy-first session recording.
CLAUDE.md for OpenReplay
## OpenReplay Stack
- Version: @openreplay/tracker >= 14.x
- Init: const tracker = new Tracker({ projectKey: process.env.NEXT_PUBLIC_OPENREPLAY_KEY!, ingestPoint: process.env.NEXT_PUBLIC_OPENREPLAY_INGEST! })
- Start: tracker.start() — call once after mount (or after user consent)
- Identity: tracker.setUserID(userId); tracker.setMetadata("plan", value)
- Custom event: tracker.event("event_name", { prop: value }) — visible in session timeline
- Privacy: add className="or-hidden" to mask DOM elements; set defaultInputMode: 1 to obscure all inputs
- Plugins: trackerNetwork (xhr recording), trackerPerf (web vitals), trackerRedux (state)
- Stop: tracker.stop() — call on sign-out
OpenReplay Tracker Client
// lib/openreplay/tracker.ts — OpenReplay singleton with typed events
import Tracker from "@openreplay/tracker"
import trackerNetwork from "@openreplay/tracker-network"
import trackerPerf from "@openreplay/tracker-perf"
let tracker: Tracker | null = null
export function getTracker(): Tracker {
if (tracker) return tracker
tracker = new Tracker({
projectKey: process.env.NEXT_PUBLIC_OPENREPLAY_KEY!,
ingestPoint: process.env.NEXT_PUBLIC_OPENREPLAY_INGEST!,
// Privacy controls
obscureTextEmails: true, // redact email addresses in text nodes
obscureInputDates: true, // redact date inputs
defaultInputMode: 0, // 0=plain, 1=obscured, 2=ignored
respectDoNotTrack: true, // honor DNT header
// Performance
captureIFrames: false,
connAttemptCount: 3,
connAttemptGap: 5000,
})
// Network request recording (redact auth tokens)
tracker.use(
trackerNetwork({
failuresOnly: false,
sessionTokenHeader: false,
ignoreHeaders: ["authorization", "cookie", "set-cookie"],
sanitizer: (data) => {
// Redact response bodies for auth endpoints
if (data.url?.includes("/api/auth")) {
data.response = "[REDACTED]"
}
return data
},
}),
)
// Core Web Vitals
tracker.use(trackerPerf())
return tracker
}
// ── Identity ───────────────────────────────────────────────────────────────
export function identifySession(
userId: string,
metadata: Record<string, string> = {},
): void {
const t = getTracker()
t.setUserID(userId)
Object.entries(metadata).forEach(([key, value]) => {
t.setMetadata(key, value)
})
}
// ── Custom events (typed) ──────────────────────────────────────────────────
type SessionEvents = {
"checkout_started": { cartValue: number; itemCount: number }
"checkout_completed": { orderId: string; revenue: number }
"search_performed": { query: string; results: number }
"feature_used": { feature: string }
"error_displayed": { code: string; message: string }
"upgrade_clicked": { from: string; to: string; location: string }
}
type SessionEventName = keyof SessionEvents
export function trackEvent<E extends SessionEventName>(
event: E,
properties: SessionEvents[E],
): void {
const t = getTracker()
t.event(event, properties as Record<string, unknown>)
}
/** Flag an issue from code — appears in OpenReplay issue tracker */
export function flagIssue(title: string, context: Record<string, unknown> = {}): void {
const t = getTracker()
t.issue(title, context)
}
export function stopRecording(): void {
tracker?.stop()
}
Next.js App Router Integration
// components/OpenReplayProvider.tsx — session replay with privacy controls
"use client"
import { useEffect, useRef } from "react"
import { usePathname } from "next/navigation"
import { getTracker, identifySession, trackEvent } from "@/lib/openreplay/tracker"
type Props = {
userId?: string
userPlan?: string
children: React.ReactNode
}
export default function OpenReplayProvider({ userId, userPlan, children }: Props) {
const pathname = usePathname()
const started = useRef(false)
// Start tracker once
useEffect(() => {
if (started.current) return
const tracker = getTracker()
tracker.start().then(() => {
started.current = true
if (userId) {
identifySession(userId, { plan: userPlan ?? "free" })
}
})
}, [userId, userPlan])
// Track route changes as custom events
useEffect(() => {
if (!started.current) return
trackEvent("feature_used", { feature: `page:${pathname}` })
}, [pathname])
return <>{children}</>
}
Redux Plugin Setup
// lib/openreplay/redux-plugin.ts — capture Redux state in session
import trackerRedux from "@openreplay/tracker-redux"
import { getTracker } from "./tracker"
import type { Store } from "redux"
/** Attach Redux store to OpenReplay — state diff visible in sessions */
export function attachReduxStore(store: Store): Store {
const tracker = getTracker()
tracker.use(
trackerRedux({
actionFilter: (action: { type: string }) =>
!action.type.startsWith("@@INIT"), // skip noisy init actions
actionTransformer: (action: unknown) => action, // optionally redact action payloads
stateTransformer: (state: Record<string, unknown>) => {
// Redact sensitive state fields
const { auth, ...safe } = state
return { ...safe, auth: { isLoggedIn: !!(auth as any)?.user } }
},
}),
)
return store
}
React Error Boundary with OpenReplay
// components/OpenReplayErrorBoundary.tsx — capture render errors in session
"use client"
import { Component, type ReactNode } from "react"
import { flagIssue } from "@/lib/openreplay/tracker"
type Props = { children: ReactNode; fallback?: ReactNode }
type State = { hasError: boolean; errorMessage?: string }
export class OpenReplayErrorBoundary extends Component<Props, State> {
state: State = { hasError: false }
static getDerivedStateFromError(error: Error): State {
return { hasError: true, errorMessage: error.message }
}
componentDidCatch(error: Error, info: { componentStack: string }) {
// Flag the issue — it appears in the session timeline and issue tracker
flagIssue(`React Error: ${error.message}`, {
errorName: error.name,
componentStack: info.componentStack.slice(0, 500),
})
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? (
<div className="p-8 text-center border border-red-200 rounded-xl bg-red-50">
<p className="text-red-600 font-medium">An unexpected error occurred.</p>
<button
className="mt-4 px-4 py-2 bg-white border border-red-300 text-red-600 rounded-lg text-sm hover:bg-red-50"
onClick={() => {
this.setState({ hasError: false })
window.location.reload()
}}
>
Reload page
</button>
</div>
)
}
return this.props.children
}
}
For the Highlight.io alternative when wanting an open-source full-stack observability tool that combines session replay with backend error monitoring and distributed tracing via OpenTelemetry — Highlight.io connects frontend session replay to backend spans in a single trace while OpenReplay focuses on deeper frontend session replay with co-browsing and a dedicated self-hosted infrastructure you can run on your own servers, see the Highlight.io guide. For the LogRocket alternative when needing the proprietary LogRocket product with ML-powered session search, rage-click heatmaps, performance dashboards, and enterprise GDPR/HIPAA compliance — LogRocket is the leading commercial session replay service while OpenReplay is its open-source self-hostable counterpart with a comparable feature set and no per-session pricing, see the LogRocket guide. The Claude Skills 360 bundle includes OpenReplay skill sets covering session recording, plugins, and privacy controls. Start with the free tier to try open-source session replay generation.