Skip to content

techiesinha/react-telemetry-open

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 ([email protected])

See LICENSE and NOTICE.