react-telemetry-open
v1.0.4
Published
React-aware, vendor-neutral observability for React 18+ applications. Zero boilerplate. OTLP/HTTP output.
Maintainers
Readme
react-telemetry-open
React-aware, vendor-neutral observability for React 18+ applications.
Zero boilerplate. OTLP output. No vendor lock-in.
Why react-telemetry-open
Existing observability tools operate below the React layer:
- Error tracking tools capture exceptions — but not render performance, component attribution, or concurrent mode timing
- RUM tools measure browser-level metrics — React-specific signals like unnecessary re-renders emerge at a layer above what standard RUM instruments
- Raw OpenTelemetry requires significant manual instrumentation to produce React-aware signals
- Development-only profilers produce no production observability data
react-telemetry-open fills this gap. It tracks signals that only exist inside React — unnecessary re-renders, component-attributed long tasks, rage clicks, ErrorBoundary catches — and exports them as standard OTLP. One package. Any backend. Zero configuration to start.
What it tracks
React signals — render count and duration per component, rage clicks, time to first interaction, ErrorBoundary crashes with componentStack
Web Vitals — FCP, LCP, FID, CLS, INP with good/needs-improvement/poor ratings per Google thresholds
Network — every fetch() and XHR call with duration, status, URL. Error rate over 60-second windows. Online/offline events
Browser — JS errors with stack traces, unhandled promise rejections, long tasks, JS heap memory
Custom — track any business event with useTrackEvent()
Context on every event — component name, route, anonymous session ID, device type, browser, OS, network quality, React version
Quick start
npm install react-telemetry-open
npx react-telemetry-open initThe CLI reads your package.json, generates telemetry.config.json, and tells you exactly what to add to your app.
Add to your app entry point (src/main.tsx):
import { TelemetryProvider } from 'react-telemetry-open'
import appConfig from '../telemetry.config.json'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<TelemetryProvider appConfig={appConfig}>
<BrowserRouter>
<App />
</BrowserRouter>
</TelemetryProvider>
</React.StrictMode>
)Open DevTools console. Telemetry appears immediately — no backend required.
▼ [react-telemetry-open] react.render.duration
Type: metric
Route: /dashboard
Duration: 2.40ms
Value: 2.4 ms
Attributes: { component: "Dashboard", renderCount: 1 }
▶ App { name: "my-app", version: "1.0.0", environment: "development" }
▶ Session { id: "550e8400-...", duration: "4s", pageViews: 1 }
▶ Device { type: "desktop", viewport: "1440×900", cpuCores: 10 }
▶ Browser { name: "Chrome", version: "124", os: "macOS 14.4" }
▶ Network { online: true, type: "4g" }Hooks
Track component renders
import { useTraceRender } from 'react-telemetry-open'
function UserDashboard() {
useTraceRender('UserDashboard')
return <div>...</div>
}Emits react.render.duration with render time, render count, and priority on every render.
Track interactions
import { useTrackInteraction } from 'react-telemetry-open'
function CheckoutButton() {
const { onClick } = useTrackInteraction('checkout-submit')
return <button onClick={onClick}>Buy Now</button>
}Tracks clicks and inputs. Automatically detects rage clicks (3+ rapid clicks = user frustration).
Track custom events
import { useTrackEvent } from 'react-telemetry-open'
function OnboardingFlow() {
const track = useTrackEvent()
const handleComplete = () => {
track('onboarding:completed', { step: 4, plan: 'pro' })
}
}Track route changes
import { useRouteTrace } from 'react-telemetry-open'
function AppRouter() {
useRouteTrace()
return <Routes>...</Routes>
}Emits route.change with fromRoute, toRoute, and navigationDurationMs on every navigation.
TelemetryProvider props
appConfig — static identity. Import your telemetry.config.json here. Generated by npx react-telemetry-open init.
config — runtime overrides. For values known only at runtime — sampling rate, consent flags, feature toggles.
import appConfig from '../telemetry.config.json'
<TelemetryProvider
appConfig={appConfig}
config={{
sampling: { rate: isSlowDevice ? 0.1 : 1.0 },
signals: { memory: hasUserConsented },
}}
>Both props are optional. Without either, the package works with defaults and shows a setup warning in development.
Production setup
Run init to generate both config files:
npx react-telemetry-open initEdit telemetry.config.prod.json:
{
"app": {
"environment": "production",
"buildId": "$VITE_BUILD_ID"
},
"exporter": {
"type": "otlp",
"url": "$VITE_OTEL_URL",
"apiKey": "$VITE_OTEL_KEY"
},
"sampling": {
"rate": 0.1
},
"debug": false
}Add to .env.production:
VITE_OTEL_URL=https://your-collector-endpoint/otlp
VITE_OTEL_KEY=your-api-key
VITE_BUILD_ID=your-git-shaSwitch config based on environment in src/main.tsx:
import appConfig from '../telemetry.config.json'
import prodConfig from '../telemetry.config.prod.json'
const config = import.meta.env.PROD ? prodConfig : appConfig
<TelemetryProvider appConfig={config}>Backend compatibility
react-telemetry-open sends standard OTLP/HTTP — works with any OTel-compatible backend.
| Backend | Direct from browser | Notes | |---|---|---| | Self-hosted OTel Collector | ✅ | Configure CORS on your collector | | Grafana Alloy (self-hosted) | ✅ | Configure CORS in alloy.config | | Honeycomb | ✅ | Works directly | | Axiom | ✅ | Works directly | | Grafana Cloud | ⚠️ | Requires a proxy — CORS not supported | | Any OTLP/HTTP endpoint | ✅ | As long as CORS headers are present |
Grafana Cloud and CORS
Grafana Cloud's OTLP gateway does not support browser CORS. A lightweight proxy is required:
Browser → your proxy (CORS enabled) → Grafana CloudThe simplest proxy is a single serverless function (Vercel/Netlify) that forwards requests with your Grafana credentials server-side. This keeps your API key off the client.
For self-hosted OTel Collector or Grafana Alloy, direct browser connections work — configure cors.allowed_origins in your collector.
Browser support
| Signal | Chrome | Edge | Firefox | Safari | |---|---|---|---|---| | Render timing | ✅ | ✅ | ✅ | ✅ | | Web Vitals (full) | ✅ | ✅ | FCP only | FCP 15.4+ | | Long tasks | ✅ | ✅ | ❌ | ❌ | | Memory | ✅ | ✅ 124+ | ❌ | ❌ | | Network | ✅ | ✅ | ✅ | ✅ | | Errors | ✅ | ✅ | ✅ | ✅ | | Resource timing | ✅ | ✅ | ✅ | ✅ |
Privacy
- Anonymous sessions — random UUIDs, never linked to user identity
- No input values — only that an interaction occurred, never what was typed
- URL sanitisation — query params stripped; numeric IDs replaced with
:id - No raw user agent — parsed to structured fields only
- Do Not Track — browser DNT setting honoured; all collection skipped when enabled
API keys and collector URLs must never be hardcoded. Use $VAR_NAME syntax — resolved from environment variables at runtime:
{
"exporter": {
"url": "$VITE_OTEL_URL",
"apiKey": "$VITE_OTEL_KEY"
}
}Contributing & Bug Reports
Found a bug? Have a feature request?
- Report a bug → Open a Bug Report
- Request a feature → Open a Feature Request
- Contribute code → Read CONTRIBUTING.md then open a Pull Request
All contributions are welcome.
Documentation
- Getting Started
- Configuration Reference
- Interpreting Your Data
- Signals Overview
- Signals Reference
- API Reference
- System Design
- Troubleshooting
License
Apache 2.0 — Copyright 2026 Abhishek Sinha ([email protected])
