Highlight.io
Highlight.io: open-source monitoring, session replay, error tracking, logging, tracing, Next.js SDK, self-hosted option
Highlight.io is an open-source, full-stack monitoring platform that unifies session replay, error tracking, logging, and tracing. Its principles are:
## Key Points
- **Open source means ownership** — you can inspect the code, self-host, and extend the platform without vendor lock-in. Transparency in monitoring tools is a feature, not a nice-to-have.
- **Full-stack correlation is the default** — a frontend error should link to its backend trace, which links to its log lines. Highlight connects these automatically via session and trace IDs.
- **Session replay is not just for bugs** — replay reveals UX confusion, rage clicks, and dead clicks that error tracking alone cannot surface.
- **Logging and tracing belong together** — structured logs emitted within a trace span are automatically correlated, so you never need to manually grep for request IDs.
- **Start managed, self-host when ready** — use the cloud offering to evaluate, then move to self-hosted when compliance, data residency, or cost requires it.
1. **Initialize on both client and server** — Highlight requires separate initialization for frontend replay and backend tracing/logging to get full-stack correlation.
2. **Set `tracingOrigins` to your API domains** — this propagates the Highlight session ID to backend requests, linking frontend sessions to backend traces.
3. **Use `networkRecording` with URL blocklists** — record request/response bodies for debugging, but block auth endpoints to avoid capturing tokens.
4. **Identify users after authentication** — `H.identify()` ties sessions to user accounts, enabling search by email and impact analysis.
5. **Wrap API routes with error handling** — use `H.consumeError()` to ensure backend errors include full context and are linked to the originating session.
6. **Use semantic span names** — `create_subscription` is better than `handler`; descriptive names make trace waterfalls readable at a glance.
7. **Deploy the feedback widget** — session feedback attaches user context directly to the replay, reducing back-and-forth in bug reports.
## Quick Example
```typescript
// lib/constants.ts
export const CONSTANTS = {
HIGHLIGHT_PROJECT_ID: process.env.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID!,
} as const;
```skilldb get monitoring-services-skills/highlightFull skill: 353 linesHighlight.io Monitoring Skill
Core Philosophy
Highlight.io is an open-source, full-stack monitoring platform that unifies session replay, error tracking, logging, and tracing. Its principles are:
- Open source means ownership — you can inspect the code, self-host, and extend the platform without vendor lock-in. Transparency in monitoring tools is a feature, not a nice-to-have.
- Full-stack correlation is the default — a frontend error should link to its backend trace, which links to its log lines. Highlight connects these automatically via session and trace IDs.
- Session replay is not just for bugs — replay reveals UX confusion, rage clicks, and dead clicks that error tracking alone cannot surface.
- Logging and tracing belong together — structured logs emitted within a trace span are automatically correlated, so you never need to manually grep for request IDs.
- Start managed, self-host when ready — use the cloud offering to evaluate, then move to self-hosted when compliance, data residency, or cost requires it.
Setup
Next.js Full-Stack Integration
// instrumentation.ts (Next.js instrumentation hook)
import { CONSTANTS } from "@/lib/constants";
export async function register() {
const { H } = await import("@highlight-run/next/server");
H.init({
projectID: CONSTANTS.HIGHLIGHT_PROJECT_ID,
serviceName: "web-api",
serviceVersion: process.env.APP_VERSION ?? "0.0.0",
environment: process.env.NODE_ENV,
});
}
// lib/constants.ts
export const CONSTANTS = {
HIGHLIGHT_PROJECT_ID: process.env.NEXT_PUBLIC_HIGHLIGHT_PROJECT_ID!,
} as const;
Client-Side Initialization
// app/providers.tsx
"use client";
import { H } from "highlight.run";
import { HighlightInit } from "@highlight-run/next/client";
import { ErrorBoundary } from "@highlight-run/react";
import { CONSTANTS } from "@/lib/constants";
export function HighlightProvider({ children }: { children: React.ReactNode }) {
return (
<>
<HighlightInit
projectId={CONSTANTS.HIGHLIGHT_PROJECT_ID}
serviceName="web-client"
tracingOrigins={["api.example.com", "localhost"]}
networkRecording={{
enabled: true,
recordHeadersAndBody: true,
urlBlocklist: [/\/api\/auth\/token/],
}}
privacySetting="default"
enableCanvasRecording={false}
enablePerformanceRecording={true}
/>
<ErrorBoundary showDialog={false}>
{children}
</ErrorBoundary>
</>
);
}
export function identifyHighlightUser(user: {
id: string;
email: string;
name: string;
}) {
H.identify(user.email, {
id: user.id,
name: user.name,
});
}
Backend Logger Setup
// lib/highlight-logger.ts
import { H } from "@highlight-run/next/server";
import winston from "winston";
class HighlightTransport extends winston.Transport {
log(info: { level: string; message: string; [key: string]: unknown }, callback: () => void) {
const { level, message, ...meta } = info;
const severity = this.mapLevel(level);
H.log(message, severity, {
...meta,
service: process.env.SERVICE_NAME ?? "api",
timestamp: new Date().toISOString(),
});
callback();
}
private mapLevel(level: string): "trace" | "debug" | "info" | "warn" | "error" | "fatal" {
const mapping: Record<string, "trace" | "debug" | "info" | "warn" | "error" | "fatal"> = {
silly: "trace",
debug: "debug",
verbose: "debug",
info: "info",
warn: "warn",
error: "error",
};
return mapping[level] ?? "info";
}
}
export const logger = winston.createLogger({
level: "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new HighlightTransport(),
...(process.env.NODE_ENV !== "production"
? [new winston.transports.Console({ format: winston.format.simple() })]
: []),
],
});
Key Techniques
API Route Error Handling with Highlight
// lib/highlight-api.ts
import { H } from "@highlight-run/next/server";
import { type NextRequest, NextResponse } from "next/server";
type ApiHandler = (req: NextRequest) => Promise<NextResponse>;
export function withHighlight(handler: ApiHandler): ApiHandler {
return async (req: NextRequest) => {
const requestId = crypto.randomUUID();
try {
const response = await handler(req);
if (response.status >= 400) {
H.log(`HTTP ${response.status} on ${req.method} ${req.nextUrl.pathname}`, "warn", {
requestId,
method: req.method,
path: req.nextUrl.pathname,
status: response.status,
});
}
return response;
} catch (error) {
H.consumeError(
error instanceof Error ? error : new Error(String(error)),
undefined,
{
requestId,
method: req.method,
path: req.nextUrl.pathname,
}
);
return NextResponse.json(
{ error: "Internal Server Error", requestId },
{ status: 500 }
);
}
};
}
// Usage
export const GET = withHighlight(async (req) => {
const data = await fetchData();
return NextResponse.json(data);
});
Custom Trace Spans
// lib/highlight-tracing.ts
import { H } from "@highlight-run/next/server";
export async function withSpan<T>(
spanName: string,
attributes: Record<string, string>,
fn: () => Promise<T>
): Promise<T> {
return H.startActiveSpan(spanName, async (span) => {
for (const [key, value] of Object.entries(attributes)) {
span.setAttribute(key, value);
}
try {
const result = await fn();
span.setStatus({ code: 1 }); // OK
return result;
} catch (error) {
span.setStatus({ code: 2, message: error instanceof Error ? error.message : "error" });
span.recordException(error instanceof Error ? error : new Error(String(error)));
throw error;
} finally {
span.end();
}
});
}
// Usage in a service
async function createSubscription(userId: string, planId: string) {
return withSpan("create_subscription", { "user.id": userId, "plan.id": planId }, async () => {
const user = await withSpan("fetch_user", { "db.operation": "findUnique" }, () =>
prisma.user.findUniqueOrThrow({ where: { id: userId } })
);
const subscription = await withSpan("stripe_create_subscription", { "stripe.plan": planId }, () =>
stripe.subscriptions.create({
customer: user.stripeCustomerId,
items: [{ price: planId }],
})
);
return withSpan("save_subscription", { "db.operation": "create" }, () =>
prisma.subscription.create({
data: { userId, stripeId: subscription.id, planId },
})
);
});
}
User Feedback Collection
// components/FeedbackWidget.tsx
"use client";
import { useState } from "react";
import { H } from "highlight.run";
export function FeedbackWidget() {
const [feedback, setFeedback] = useState("");
const [submitted, setSubmitted] = useState(false);
function handleSubmit() {
if (!feedback.trim()) return;
H.addSessionFeedback({
verbatim: feedback,
userName: "current-user",
userEmail: "user@example.com",
timestamp: new Date().toISOString(),
});
setSubmitted(true);
setFeedback("");
setTimeout(() => setSubmitted(false), 3000);
}
return (
<div className="fixed bottom-4 right-4 p-4 bg-white rounded-lg shadow-lg">
{submitted ? (
<p className="text-green-600">Thank you for your feedback!</p>
) : (
<>
<textarea
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
placeholder="Report an issue or share feedback..."
className="w-64 h-24 p-2 border rounded"
/>
<button
onClick={handleSubmit}
className="mt-2 w-full px-4 py-2 bg-blue-600 text-white rounded"
>
Send Feedback
</button>
</>
)}
</div>
);
}
Self-Hosted Docker Compose Setup
// scripts/generate-highlight-compose.ts
// Helper to generate docker-compose for self-hosted Highlight
interface HighlightConfig {
adminEmail: string;
licenseKey: string;
domain: string;
storageBackend: "s3" | "gcs" | "local";
}
function generateComposeEnv(config: HighlightConfig): string {
return [
`ADMIN_EMAIL=${config.adminEmail}`,
`LICENSE_KEY=${config.licenseKey}`,
`REACT_APP_FRONTEND_URI=https://${config.domain}`,
`REACT_APP_PUBLIC_GRAPH_URI=https://${config.domain}/public`,
`REACT_APP_PRIVATE_GRAPH_URI=https://${config.domain}/private`,
`SSL=true`,
`OBJECT_STORAGE_FS=${config.storageBackend === "local" ? "/highlight-data" : ""}`,
`IN_DOCKER=true`,
].join("\n");
}
// Self-host deployment:
// 1. git clone https://github.com/highlight/highlight
// 2. cd docker && cp .env.example .env
// 3. Edit .env with the values above
// 4. docker compose up -d
Best Practices
- Initialize on both client and server — Highlight requires separate initialization for frontend replay and backend tracing/logging to get full-stack correlation.
- Set
tracingOriginsto your API domains — this propagates the Highlight session ID to backend requests, linking frontend sessions to backend traces. - Use
networkRecordingwith URL blocklists — record request/response bodies for debugging, but block auth endpoints to avoid capturing tokens. - Identify users after authentication —
H.identify()ties sessions to user accounts, enabling search by email and impact analysis. - Wrap API routes with error handling — use
H.consumeError()to ensure backend errors include full context and are linked to the originating session. - Use semantic span names —
create_subscriptionis better thanhandler; descriptive names make trace waterfalls readable at a glance. - Deploy the feedback widget — session feedback attaches user context directly to the replay, reducing back-and-forth in bug reports.
- Evaluate self-hosting for compliance — if data residency or HIPAA compliance is required, self-hosted Highlight keeps all data on your infrastructure.
Anti-Patterns
- Client-only initialization — without server-side setup, backend errors and logs are invisible; you only see half the picture.
- Recording everything without privacy settings — default privacy mode masks user input; disabling it without review risks capturing sensitive data in replays.
- Ignoring
tracingOrigins— without it, frontend requests and backend traces are disconnected, losing the full-stack correlation that is Highlight's main advantage. - Not blocking auth URLs in network recording — tokens and credentials in recorded network payloads are a security risk if session data is accessed broadly.
- Self-hosting without resource planning — Highlight stores session replay data, which is large; plan for significant storage growth and set retention policies.
- Using Highlight for infrastructure metrics — Highlight excels at application-level observability; for host/container metrics, pair it with Prometheus or a dedicated infrastructure tool.
- Skipping error boundaries — without
ErrorBoundary, React crashes unmount the Highlight recorder, and you lose the final moments of the session before the error.
Install this skill directly: skilldb add monitoring-services-skills
Related Skills
Honeycomb
Honeycomb is an observability platform designed for complex, distributed systems. It allows you to analyze high-cardinality, wide event data to understand system behavior, debug issues, and explore unknown unknowns in production with unparalleled depth and speed.
LogRocket
LogRocket: session replay, error tracking, performance monitoring, Redux/Vuex integration, network logging
New Relic
New Relic: APM, distributed tracing, browser monitoring, custom events, NRQL queries, alert policies, Node.js and browser agents
Opentelemetry
OpenTelemetry provides a set of open-source APIs, SDKs, and tools to instrument, generate, collect, and export telemetry data (metrics, logs, traces) for observability. Use it to standardize your application's telemetry across various services and vendors without vendor lock-in.
Prometheus
Open-source monitoring and alerting toolkit for collecting metrics via pull-based scraping, building PromQL queries, setting up alerts, and instrumenting applications for cloud-native observability.
Sentry
Sentry: error tracking, performance monitoring, session replay, source maps, breadcrumbs, Next.js/React SDK, alerts