Highlight.io provides full-stack session replay, error monitoring, and distributed tracing — H.init(projectId, { tracingOrigins: true, networkRecording: { enabled: true } }) initializes the frontend SDK. H.identify(userId, { email, name }) associates sessions with users. H.track("event_name", { property: value }) records custom events. H.consumeError(error) manually captures exceptions. Backend Node.js: import { H } from "@highlight-run/node", H.init({ projectID }) in instrumentation.ts, then H.runWithHeaders("operation-name", headers, async () => { /* code */ }) links backend traces to the frontend session. Errors from console.error and unhandled rejections are captured automatically. Next.js App Router: add withHighlightConfig(nextConfig) in next.config.js, create app/api/highlight-tunnel/route.ts as a proxy (avoids ad blockers). <HighlightInit projectId="..." /> in the root layout. Privacy: H.init({ privacyMode: true }) masks all text — or use highlight-block CSS class on specific elements. OpenTelemetry: Highlight is OTel-compatible — wrap spans with H.runWithHeaders and spans appear in the Highlight timeline linked to the user session. Claude Code generates Highlight.io frontend init, backend instrumentation, and distributed trace correlation.
CLAUDE.md for Highlight.io
## Highlight.io Stack
- Frontend version: @highlight-run/next >= 8.x (includes React SDK)
- Backend version: @highlight-run/node >= 4.x
- Frontend init: <HighlightInit projectId={PROJECT_ID} serviceName="frontend" tracingOrigins={true} networkRecording={{ enabled: true, recordHeadersAndBody: true }} />
- Identify: H.identify(userId, { email, name, plan })
- Custom event: H.track("clicked_upgrade", { plan: "pro" })
- Manual error: H.consumeError(new Error("payment failed"))
- Backend: H.init({ projectID }) in instrumentation.ts; wrap handlers with H.runWithHeaders(name, req.headers, fn)
- Tunnel route: required in Next.js to avoid ad-blockers — proxy /api/highlight-tunnel to Highlight ingress
Frontend Setup (Next.js App Router)
// app/layout.tsx — Highlight.io root layout instrumentation
import { HighlightInit } from "@highlight-run/next/client"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<HighlightInit
projectId={process.env.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID!}
serviceName="my-app-frontend"
tracingOrigins
networkRecording={{
enabled: true,
recordHeadersAndBody: true,
// Redact auth headers from network recordings
requestResponseSanitizer: (pair) => {
if (pair.request.headers["authorization"]) {
pair.request.headers["authorization"] = "[REDACTED]"
}
return pair
},
}}
privacyMode={false}
// Mask specific DOM elements with CSS class 'highlight-block'
manualSnippetInjection={false}
/>
</head>
<body>{children}</body>
</html>
)
}
Highlight Client Utilities
// lib/highlight/client.ts — H usage helpers
"use client"
import { H } from "@highlight-run/next/client"
/** Identify a logged-in user — call after successful auth */
export function identifyUser(user: {
id: string
email: string
name?: string
plan?: string
createdAt?: string
}) {
H.identify(user.id, {
email: user.email,
name: user.name ?? "",
plan: user.plan ?? "free",
createdAt: user.createdAt ?? "",
})
}
/** Track a custom business event */
export function trackEvent(
name: string,
properties: Record<string, string | number | boolean> = {},
) {
H.track(name, properties)
}
/** Manually capture a non-critical error with context */
export function captureError(error: Error | unknown, context: Record<string, unknown> = {}) {
const err = error instanceof Error ? error : new Error(String(error))
// Attach context as a span attribute before consuming
H.track("error_captured", {
errorName: err.name,
errorMessage: err.message,
...Object.fromEntries(
Object.entries(context).map(([k, v]) => [k, String(v)])
),
})
H.consumeError(err)
}
/** React error boundary integration */
export function onError(error: Error, errorInfo: { componentStack: string }) {
H.consumeError(error, undefined, { componentStack: errorInfo.componentStack })
}
React Error Boundary
// components/ErrorBoundary.tsx — catch React render errors
"use client"
import { Component, type ReactNode } from "react"
import { onError } from "@/lib/highlight/client"
type Props = { children: ReactNode; fallback?: ReactNode }
type State = { hasError: boolean; error?: Error }
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false }
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, info: { componentStack: string }) {
onError(error, info)
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? (
<div className="p-6 rounded-xl border border-red-200 bg-red-50 text-center">
<h2 className="font-semibold text-red-700 mb-2">Something went wrong</h2>
<p className="text-sm text-red-600">{this.state.error?.message}</p>
<button
className="mt-4 px-4 py-2 bg-red-100 text-red-700 rounded-lg text-sm hover:bg-red-200"
onClick={() => this.setState({ hasError: false })}
>
Try again
</button>
</div>
)
}
return this.props.children
}
}
Backend Node.js Instrumentation
// instrumentation.ts — Next.js instrumentation hook (runs before app)
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { H } = await import("@highlight-run/node")
H.init({
projectID: process.env.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID!,
serviceName: "my-app-backend",
serviceVersion: process.env.npm_package_version,
environment: process.env.NODE_ENV,
})
}
}
// app/api/orders/route.ts — backend API with Highlight tracing
import { NextResponse } from "next/server"
import { H } from "@highlight-run/node"
import { db } from "@/lib/db"
import { auth } from "@/lib/auth"
export async function POST(req: Request) {
const session = await auth()
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
// H.runWithHeaders links this backend trace to the frontend session
return H.runWithHeaders("POST /api/orders", req.headers, async () => {
try {
const body = await req.json()
const order = await db.order.create({
data: { userId: session.user.id, ...body },
})
return NextResponse.json(order, { status: 201 })
} catch (err) {
H.consumeError(err instanceof Error ? err : new Error(String(err)))
return NextResponse.json({ error: "Failed to create order" }, { status: 500 })
}
})
}
Highlight Tunnel Route
// app/api/highlight-tunnel/route.ts — proxy to avoid ad-blockers
import { NextHighlightHandler } from "@highlight-run/next/server"
export const { GET, OPTIONS, POST } = NextHighlightHandler()
// next.config.js — wrap config with Highlight source map uploads
const { withHighlightConfig } = require("@highlight-run/next/config")
/** @type {import('next').NextConfig} */
const nextConfig = { reactStrictMode: true }
module.exports = withHighlightConfig(nextConfig)
For the Sentry alternative when needing deep error monitoring with release tracking, source map upload, performance traces with automatic SDK instrumentation for Next.js/Express, and a large ecosystem of third-party integrations and alerting rules — Sentry is the market leader in error monitoring while Highlight.io’s unique strength is the session replay that lets you watch exactly what a user did before an error, see the Sentry guide. For the LogRocket alternative when needing session replay with ML-powered session search, rage-click detection, and strict GDPR-compliant EU data residency options — LogRocket is a mature proprietary session replay product while Highlight.io is fully open-source and self-hostable with similar session replay capabilities, see the LogRocket guide. The Claude Skills 360 bundle includes Highlight.io skill sets covering session replay, error monitoring, and distributed tracing. Start with the free tier to try full-stack observability generation.