SpitfireLoad Testing & Observabilityv1.1.0 · What's new

Version: 1.1.0This documentation is for Spitfire 1.1.0.

Connecting a React.js app (browser) with OTel

React in the browser: page load, fetch/XHR and clicks as traces, linked to your backend with traceparent. Values: Connecting services with OTel: overview.

Version table

Build Package Version
Vite 5+, Next.js 15.3+, Create React App 5 @opentelemetry/sdk-trace, core, resources, context-zone 3.0.0
@opentelemetry/exporter-trace-otlp-http, instrumentation, instrumentation-fetch, instrumentation-xml-http-request 0.300.0
@opentelemetry/instrumentation-document-load 0.69.0
@opentelemetry/instrumentation-user-interaction + zone.js 0.68.0 + 0.16.3
@opentelemetry/api 1.10.0
Build machine Node.js 22.15 or later
Browser ES2022 (Chrome 94, Firefox 93, Safari 16.4 and later) –

Find your version: node --version

Traces only. OTel marks browser instrumentation as experimental.

Verified: 2026-11-04. Source: OTel JS browser, OTel JS 3.x migration guide, npm @opentelemetry/sdk-trace, npm @opentelemetry/instrumentation-document-load, npm @opentelemetry/instrumentation-user-interaction, Next.js instrumentation-client.

What you need

  • Spitfire address https://<spitfire>
  • Service name <service-name>, version <service-version>, environment <environment>
  • The app folder (here /srv/web, where package.json is) and its address (here https://<app-origin>)
  • Your API address, as a regular expression (here ^https://api\.example\.com/)

Vite

  1. Create the key: Observability → Browser and app key, Allowed origins https://<app-origin>; copy sfrum_… (Observability).

  2. Add the packages:

bash
cd /srv/web && npm i @opentelemetry/api@1.10.0 @opentelemetry/sdk-trace@3.0.0 @opentelemetry/core@3.0.0 @opentelemetry/resources@3.0.0 @opentelemetry/context-zone@3.0.0 @opentelemetry/exporter-trace-otlp-http@0.300.0 @opentelemetry/instrumentation@0.300.0 @opentelemetry/instrumentation-fetch@0.300.0 @opentelemetry/instrumentation-xml-http-request@0.300.0 @opentelemetry/instrumentation-document-load@0.69.0 @opentelemetry/instrumentation-user-interaction@0.68.0 zone.js@0.16.3
  1. Create /srv/web/.env.production and paste:
bash
VITE_IS_OTEL_ENABLED=true
VITE_OTEL_SERVICE_NAME=<service-name>
VITE_OTEL_SERVICE_VERSION=<service-version>
VITE_OTEL_ENVIRONMENT=<environment>
VITE_OTEL_TRACES_ENDPOINT=https://<spitfire>/otlp/v1/traces
VITE_OTEL_KEY=sfrum_…
VITE_OTEL_TRACES_SAMPLER_ARG=0.1
VITE_OTEL_PROPAGATE_URLS=^https://api\.example\.com/
  1. Create /srv/web/src/otel.ts and paste:
ts
// VITE_IS_OTEL_ENABLED=true: OTel on; any other value: off.
import { context, propagation, trace } from '@opentelemetry/api';
import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { BatchSpanProcessor, ParentBasedSampler, TraceIdRatioBasedSampler, TracerProvider } from '@opentelemetry/sdk-trace';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { ZoneContextManager } from '@opentelemetry/context-zone';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load';
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction';
import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';

const env = import.meta.env;
if (env.VITE_IS_OTEL_ENABLED === 'true') {
  const provider = new TracerProvider({
    resource: resourceFromAttributes({
      'service.name': env.VITE_OTEL_SERVICE_NAME,
      'service.version': env.VITE_OTEL_SERVICE_VERSION,
      'deployment.environment': env.VITE_OTEL_ENVIRONMENT,
    }),
    sampler: new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(Number(env.VITE_OTEL_TRACES_SAMPLER_ARG)) }),
    spanProcessors: [new BatchSpanProcessor({
      exporter: new OTLPTraceExporter({ url: env.VITE_OTEL_TRACES_ENDPOINT, headers: { Authorization: `Bearer ${env.VITE_OTEL_KEY}` } }),
    })],
  });
  trace.setGlobalTracerProvider(provider);
  context.setGlobalContextManager(new ZoneContextManager().enable());
  propagation.setGlobalPropagator(new CompositePropagator({ propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()] }));
  const propagateTraceHeaderCorsUrls = [new RegExp(env.VITE_OTEL_PROPAGATE_URLS)];
  registerInstrumentations({
    instrumentations: [
      new DocumentLoadInstrumentation(),
      new UserInteractionInstrumentation({ eventNames: ['click', 'submit'] }),
      new FetchInstrumentation({ propagateTraceHeaderCorsUrls }),
      new XMLHttpRequestInstrumentation({ propagateTraceHeaderCorsUrls }),
    ],
  });
}
  1. Make it the first line of /srv/web/src/main.tsx:
ts
import './otel';
  1. Build:
bash
cd /srv/web && npm run build

Next.js

  1. Vite steps 1 and 2, then create /srv/web/.env.production from Vite step 3 with NEXT_PUBLIC_ in place of VITE_:
bash
sed -i.bak 's/^VITE_/NEXT_PUBLIC_/' /srv/web/.env.production
  1. Save the Vite step 4 code as /srv/web/instrumentation-client.ts and change the prefix:
bash
perl -0pi -e 's/const env = import\.meta\.env;\n//; s/\benv\.VITE_/process.env.VITE_/g; s/VITE_/NEXT_PUBLIC_/g' /srv/web/instrumentation-client.ts
  1. Build:
bash
cd /srv/web && npm run build

Create React App

  1. Vite steps 1, 2 and 4, then change the prefix of .env.production and src/otel.ts:
bash
sed -i.bak 's/^VITE_/REACT_APP_/' /srv/web/.env.production && perl -0pi -e 's/const env = import\.meta\.env;\n//; s/\benv\.VITE_/process.env.VITE_/g; s/VITE_/REACT_APP_/g' /srv/web/src/otel.ts
  1. Make import './otel'; the first line of /srv/web/src/index.tsx, then build:
bash
cd /srv/web && npm run build

Your backend

  1. Let the backend's CORS allow the trace headers from https://<app-origin>:
text
Access-Control-Allow-Headers: traceparent, tracestate, baggage

Verify

  1. The browser may send:
bash
curl -s -o /dev/null -w '%{http_code}' -X OPTIONS https://<spitfire>/otlp/v1/traces -H 'Origin: https://<app-origin>' -H 'Access-Control-Request-Method: POST' | grep -q '^204
  

 && echo OK
  1. Spitfire takes the key:
bash
curl -s -o /dev/null -w '%{http_code}' -X POST https://<spitfire>/otlp/v1/traces -H 'Origin: https://<app-origin>' -H 'Authorization: Bearer sfrum_…' -H 'Content-Type: application/json' -d '{}' | grep -q '^200
  

 && echo OK
  1. The build carries the endpoint:
bash
grep -rq '/otlp/v1/traces' /srv/web/dist /srv/web/build /srv/web/.next/static 2>/dev/null && echo OK

Turning off and removing

  1. Turn off: in .env.production write false for VITE_IS_OTEL_ENABLED (NEXT_PUBLIC_…, REACT_APP_…), then build again.
  2. Remove:
bash
cd /srv/web && npm uninstall @opentelemetry/api @opentelemetry/sdk-trace @opentelemetry/core @opentelemetry/resources @opentelemetry/context-zone @opentelemetry/exporter-trace-otlp-http @opentelemetry/instrumentation @opentelemetry/instrumentation-fetch @opentelemetry/instrumentation-xml-http-request @opentelemetry/instrumentation-document-load @opentelemetry/instrumentation-user-interaction zone.js && rm -f src/otel.ts instrumentation-client.ts

Common problems

Symptom Fix
Verify step 1 prints nothing Add https://<app-origin> to the key's Allowed origins; wait 30 s
401 The key is sfrum_…, enabled, after Bearer
429 Raise the key's Exports per minute or lower VITE_OTEL_TRACES_SAMPLER_ARG
Your API calls fail with CORS Your backend step 1
Browser and backend spans are not one trace VITE_OTEL_PROPAGATE_URLS must match your API address
npm i fails with EBADENGINE Node.js 22.15 or later on the build machine