OpenTelemetry in a Vue App: What Works Today, and What Is Still Experimental (2026)

  • vuetelemetry
  • Guides
  • 9 min read

OpenTelemetry can trace a Vue app from document load to fetch call, but the browser story is far less settled than the server one. Which packages are stable, what auto-instrumentation really captures, the Vue-shaped gap nobody fills, and where the data ends up.

Your Vue app runs on someone else's machine, on a network you do not control, in a browser version you did not choose. Server logs tell you a request arrived and a response left; they tell you nothing about the four seconds the user spent staring at a spinner. OpenTelemetry is the vendor-neutral standard for closing that gap: one way to produce traces, metrics and logs, and any backend that speaks the protocol can receive them.

The payoff is a single trace that starts in the browser and ends in your database. A document-load span, then the fetch the page fires, then the server span that fetch triggered, all stitched together by trace context propagated in HTTP headers. That is the thing you cannot get from Vue devtools or from your APM's server-side agent alone, and it is why people reach for OpenTelemetry on the front end at all.

Stable SDK, unsettled story

A close-up of JavaScript code using jQuery to read the window scroll position - the kind of browser-side work an instrumentation has to observe from the outside.
A close-up of JavaScript code using jQuery to read the window scroll position - the kind of browser-side work an instrumentation has to observe from the outside.

Now the honest part, because it is routinely glossed over. The browser tracing SDK, `@opentelemetry/sdk-trace-web`, is marked stable in the OpenTelemetry JS repository. But the official documentation is blunt about the wider picture: "Client instrumentation for the browser is experimental and mostly unspecified." Those two statements are both true and they are not in conflict. The plumbing that creates and exports spans is settled; the conventions for what a browser span should contain, and which signals a front end ought to emit, are not.

There is also a newer effort worth knowing about before you build on assumptions. The `open-telemetry/opentelemetry-browser` repository publishes `@opentelemetry/browser-instrumentation`, and its status is listed as experimental. Its approach differs from the older packages: it emits structured log records for browser performance and user interactions, as a complement to the span-based instrumentations maintained elsewhere. If you are starting today, build on the stable span path and treat the event-based one as something to watch, not to depend on.

The packages and the wiring

The working set of packages is small. `@opentelemetry/api` gives you the tracer interface, `@opentelemetry/sdk-trace-web` the browser provider, `@opentelemetry/sdk-trace-base` the span processors and the console exporter, `@opentelemetry/context-zone` the context manager, and `@opentelemetry/instrumentation` the registration helper. Individual instrumentations ship separately, as do the OTLP exporters. Nothing here is Vue-specific, which is the first clue about what follows.

  • `@opentelemetry/sdk-trace-web` is stable, but the official docs state that client instrumentation for the browser is experimental and mostly unspecified
  • Three calls to wire it: create `WebTracerProvider` with span processors, `provider.register()` with `ZoneContextManager`, then `registerInstrumentations()`
  • Ready-made coverage is document load, user interactions, XHR and fetch - bundled in `@opentelemetry/auto-instrumentations-web`
  • There is no official Vue instrumentation: component and router spans are code you write and maintain yourself
  • `@opentelemetry/browser-instrumentation` (the newer opentelemetry-browser repo) is experimental and emits structured log records rather than spans
  • `SimpleSpanProcessor` plus console for development, `BatchSpanProcessor` plus an OTLP exporter for production

The wiring is three calls. You create the provider with its span processors, for example `new WebTracerProvider({ spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())] })`. You register it with a context manager, `provider.register({ contextManager: new ZoneContextManager() })`. Then you register your instrumentations with `registerInstrumentations({ instrumentations: [new DocumentLoadInstrumentation()] })`. Console and `SimpleSpanProcessor` are for development; production wants `BatchSpanProcessor` and an OTLP exporter, so spans leave in batches instead of one request per span.

What auto-instrumentation captures

What the ready-made instrumentations actually capture is narrower than people expect. `@opentelemetry/instrumentation-document-load` produces spans for document load and navigation timing. `@opentelemetry/instrumentation-user-interaction` traces user interactions such as clicks. `@opentelemetry/instrumentation-xml-http-request` and the fetch equivalent cover outgoing requests. `@opentelemetry/auto-instrumentations-web` bundles them so you register one thing instead of four. Between them you get page load, clicks and network calls - real coverage, but coverage of the browser, not of your components.

The context manager deserves a moment. `ZoneContextManager` exists because asynchronous context does not propagate on its own in a browser: without it, a span started before an await frequently loses its parent, and your trace fragments into orphans. It relies on zone.js, which means shipping and running that library in your bundle. That is a real cost in payload and in patched globals, and it is the sort of trade-off worth measuring rather than assuming, particularly on a stack that spends effort on Core Web Vitals.

The context manager deserves a moment. `ZoneContextManager` exists because asynchronous context does not propagate on its own in a browser: without it, a span started before an await frequently loses its parent, and your trace fragments into orphans. It relies on zone.js, which means shipping and running that library in your bundle. That is a real cost in payload and in patched globals, and it is the sort of trade-off worth measuring rather than assuming, particularly on a stack that spends effort on Core Web Vitals.

- vuetelemetry

The Vue-shaped gap

Now the gap. There is no official Vue instrumentation. Not in the core repository, not in the contrib packages. What circulates in blog posts and vendor guides as "Vue instrumentation" is hand-written code: spans opened in lifecycle hooks, spans wrapped around router navigation guards, a plugin that starts a span per component render. That is a perfectly reasonable thing to build, and it can be genuinely useful, but you should know you are writing and maintaining it yourself, with no semantic conventions to conform to and nothing upstream to inherit fixes from.

Where the data goes is the last decision, and the one that determines whether any of this pays off. The exporter speaks OTLP over HTTP to a collector or straight to a backend; from there the choice of tool is yours precisely because the standard is vendor-neutral. Start smaller than you think: `ConsoleSpanExporter` until spans look right in devtools, then document-load and fetch to a real backend, then decide whether component-level spans earn their keep. Instrumenting everything on day one is how front-end observability projects end up as noise nobody reads.

Related stack