npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@invinite/otel-web

v2.1.2

Published

Lightweight OpenTelemetry initialization for browser SPAs. Sets up tracing with a single `initialize()` call and provides a plugin system for framework-specific instrumentation.

Downloads

959

Readme

@invinite/otel-web

Lightweight OpenTelemetry initialization for browser SPAs. Sets up tracing with a single initialize() call and provides a plugin system for framework-specific instrumentation.

Why

Setting up OpenTelemetry in the browser requires wiring together a provider, exporter, resource, and span processors. This library reduces that to a single function call while keeping full control through a plugin system.

Install

npm install @invinite/otel-web @opentelemetry/api

@opentelemetry/api is a peer dependency — you should have exactly one copy in your app.

Quick Start

import { initialize } from "@invinite/otel-web";
import { createDocumentLoadPlugin } from "@invinite/otel-web/plugins/document-load";
import { createFetchPlugin } from "@invinite/otel-web/plugins/fetch";

const cleanup = initialize({
  collectorUrl: "https://otel-collector.example.com",
  serviceName: "my-app",
  serviceVersion: "1.2.0",
  environment: "production",
  plugins: [
    createDocumentLoadPlugin(),
    createFetchPlugin({
      propagateToUrls: [/api\.example\.com/],
    }),
  ],
});

// Call cleanup() on app teardown

Configuration

interface OtelWebConfig {
  /** Base OTLP HTTP URL (e.g. "https://collector.example.com"). Signal paths are appended automatically. */
  collectorUrl: string;
  /** Service name reported in spans */
  serviceName: string;
  /** Service version, added as a resource attribute */
  serviceVersion?: string;
  /** Deployment environment name (e.g. "production", "staging"), added as a resource attribute */
  environment?: string;
  /** Trace sampling rate between 0 and 1. Defaults to 1 (sample everything). */
  sampleRate?: number;
  /** Optional headers sent with every export request */
  headers?: Record<string, string>;
  /** Plugins to activate */
  plugins?: OtelWebPlugin[];
  /** Called on every span start — return attributes to attach */
  getSpanAttributes?: () => SpanAttributes;
  /** Enable experimental logging support (requires @opentelemetry/api-logs) */
  enableLogging?: boolean;
}

The collectorUrl follows the OTLP exporter specification — signal paths (/v1/traces, /v1/logs) are appended automatically. A custom path prefix is preserved: https://collector.example.com/mycollector becomes https://collector.example.com/mycollector/v1/traces.

Dynamic Span Attributes

getSpanAttributes is called at span creation time, so it can return values that change over the session (e.g., user identity after login):

initialize({
  collectorUrl: "https://otel-collector.example.com",
  serviceName: "my-app",
  getSpanAttributes: () => ({
    "session.id": sessionId,
    "user.id": auth.currentUser?.id ?? "",
  }),
});

Plugins

Fetch & XHR

Auto-instruments fetch() and XMLHttpRequest. Creates a span for every request with method, URL, and status code. OTLP collector URLs are automatically excluded from tracing.

import { createFetchPlugin } from "@invinite/otel-web/plugins/fetch";

createFetchPlugin({
  // Skip tracing for specific URLs (collector URLs are auto-ignored)
  ignoreUrls: [/\/health/],
  // Propagate W3C trace context headers for distributed tracing
  propagateToUrls: [/api\.example\.com/],
});

Distributed Tracing with propagateToUrls

To connect frontend traces to your backend, use propagateToUrls to specify which URLs should receive the W3C traceparent header. The fetch plugin automatically injects the header using its own span context — no manual span wrapping needed.

createFetchPlugin({
  // Inject traceparent into requests matching these patterns
  propagateToUrls: [/\/api\//, /api\.example\.com/],
});

Your backend needs to extract the traceparent header to continue the trace. Most OpenTelemetry server SDKs do this automatically. The trace_id in the header links the frontend and backend spans into a single distributed trace.

Document Load

Emits spans for page load performance metrics from the Performance API.

import { createDocumentLoadPlugin } from "@invinite/otel-web/plugins/document-load";

createDocumentLoadPlugin();

Spans emitted:

| Span | Attributes | | --- | --- | | document.load | DNS, connect, TLS, TTFB, response, DOM interactive, DOM content loaded, load event, transfer sizes | | first-paint | Time to first paint | | first-contentful-paint | Time to first contentful paint | | largest-contentful-paint | LCP time, element, size |

SSR Trace Context

When server-side rendering, you can connect browser spans to the server trace by injecting W3C trace context meta tags into the HTML:

<meta name="traceparent" content="00-<trace-id>-<span-id>-01" />
<!-- Optional: -->
<meta name="tracestate" content="vendor=value" />

The document-load plugin automatically reads these meta tags and uses them as the parent context for all page load spans. No additional configuration is needed.

TanStack Router

Creates spans for route navigations.

import { createRouterPlugin } from "@invinite/otel-web/plugins/tanstack-router";

createRouterPlugin(router, {
  // Skip tracing for specific routes
  ignoreRoutes: [/\/health/],
});

TanStack Query

Creates spans for query fetches and mutations with lifecycle tracking.

import { createQueryPlugin } from "@invinite/otel-web/plugins/tanstack-query";

createQueryPlugin(queryClient, {
  // Skip tracing for specific queries (matches against query hash)
  ignoreQueries: [/health/],
  // Skip tracing for specific mutations (matches against mutation key)
  ignoreMutations: [/analytics/],
});

Note: The query plugin traces the query/mutation lifecycle but does not instrument the underlying HTTP requests. If you need distributed tracing (i.e., traceparent propagation to your backend), use both the query plugin and the fetch plugin together.

Error Handler

Captures uncaught errors and unhandled promise rejections as error spans.

import { createErrorHandlerPlugin } from "@invinite/otel-web/plugins/error-handler";

createErrorHandlerPlugin({
  // Skip tracing for specific errors (matches against error message)
  ignoreErrors: [/ResizeObserver loop/],
});

Uses addEventListener (not direct assignment) so it won't interfere with existing error handlers.

Note: React and frameworks like TanStack Router catch rendering errors internally via error boundaries, so they never reach window.onerror. To report these errors, hook into your framework's error boundary. For example with TanStack Router:

import { SpanStatusCode, trace } from "@opentelemetry/api";
import { ErrorComponent, createRootRoute } from "@tanstack/react-router";
import { useEffect } from "react";

const RootErrorComponent = ({ error }: { error: Error }) => {
  useEffect(() => {
    const tracer = trace.getTracer("my-app");
    const span = tracer.startSpan("error-boundary");
    span.setAttribute("error.type", error.name);
    span.setAttribute("error.message", error.message);
    span.setAttribute("error.stack", error.stack ?? "");
    span.recordException(error);
    span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
    span.end();
  }, [error]);

  return <ErrorComponent error={error} />;
};

export const Route = createRootRoute({
  errorComponent: RootErrorComponent,
});

Logging (Experimental)

Note: OpenTelemetry browser logging is experimental. The API may change in future releases.

To enable logging, set enableLogging: true and install the @opentelemetry/api-logs peer dependency:

npm install @opentelemetry/api-logs
const cleanup = initialize({
  collectorUrl: "https://otel-collector.example.com",
  serviceName: "my-app",
  enableLogging: true,
});

Logs are exported to <collectorUrl>/v1/logs. Once initialized, get a logger anywhere in your app:

import { logs, SeverityNumber } from "@opentelemetry/api-logs";

const logger = logs.getLogger("my-app");

logger.emit({
  severityNumber: SeverityNumber.INFO,
  severityText: "INFO",
  body: "User completed checkout",
  attributes: { "cart.items": 3 },
});

Custom Spans

After initialize() is called, the global @opentelemetry/api tracer is available anywhere in your app for manual instrumentation:

import { trace, SpanStatusCode } from "@opentelemetry/api";

const tracer = trace.getTracer("my-app");

// Track a user action
const span = tracer.startSpan("checkout.submit", {
  attributes: { "cart.items": 3, "cart.total": 99.99 },
});
span.setStatus({ code: SpanStatusCode.OK });
span.end();

// Record an error
const errorSpan = tracer.startSpan("payment.process");
try {
  await processPayment();
  errorSpan.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
  errorSpan.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
  errorSpan.recordException(error);
  throw error;
} finally {
  errorSpan.end();
}

These spans are exported to the same endpoint and carry any attributes from getSpanAttributes.

Custom Plugins

import type { OtelWebPlugin } from "@invinite/otel-web";

const myPlugin: OtelWebPlugin = {
  setup(tracer) {
    // Subscribe to events, start spans
  },
  teardown() {
    // Unsubscribe, clean up
  },
};

License

MIT