Skip to content

Latest commit

Β 

History

21 Commits

Folders and files

Repository files navigation

react-telemetry-open

React-aware, vendor-neutral observability for React 18+ applications.

Zero boilerplate. OTLP output. No vendor lock-in.

npm version npm downloads Build License OpenTelemetry Registry


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 init

The 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 init

Edit 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-sha

Switch 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 Cloud

The 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?

All contributions are welcome.


Documentation


License

Apache 2.0 β€” Copyright 2026 Abhishek Sinha (sinha@live.in)

See LICENSE and NOTICE.