Sending Traces from your frontend application

This documentation provides steps for sending traces from your frontend application to SigNoz.

SigNoz natively supports OpenTelemetry for collecting traces, so you can instrument your frontend applications with minimal setup and get deep visibility into user interactions and network activity.

Client side traces help you understand how user actions trigger requests, how long they take, and where latency is introduced, whether in the browser, network, or backend. This gives you a complete picture of user journeys and performance across your stack.

Prerequisites

  • SigNoz Cloud or self-hosted account
  • A web application from where you want to send traces

Setup

Step 1: Setup OTel Collector

Install the OpenTelemetry Collector binary using these instructions. The Collector acts as an agent that receives, processes, and exports telemetry data. It is required to collect data from your application, including traces.

You would also need to update the collector config to whitelist the frontend domain. This is required to allow Cross-Origin Resource Sharing (CORS) requests from your frontend application to the OpenTelemetry collector.

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
        cors:
          allowed_origins:
            - <<YOUR_FRONTEND_URL>>
          allowed_headers: ['*']

Step 2: Install dependencies

Install the following dependencies.

npm i \
  @opentelemetry/sdk-trace-base \
  @opentelemetry/sdk-trace-web \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/instrumentation \
  @opentelemetry/instrumentation-fetch \
  @opentelemetry/instrumentation-xml-http-request \
  @opentelemetry/context-zone \
  @opentelemetry/resources \
  @opentelemetry/instrumentation-user-interaction
Read more about the dependencies
  • @opentelemetry/sdk-trace-base: Contains the core tracing SDK implementation including BatchSpanProcessor and base classes. This provides the foundation for creating and processing trace spans.

  • @opentelemetry/sdk-trace-web: Provides the WebTracerProvider specifically designed for browser environments. This is essential for web applications as it handles browser-specific tracing requirements.

  • @opentelemetry/exporter-trace-otlp-http: Implements the OTLP (OpenTelemetry Protocol) HTTP exporter that sends your traces to the SigNoz collector. This handles the actual transmission of trace data over HTTP.

  • @opentelemetry/instrumentation: Provides the registerInstrumentations function that allows you to register multiple instrumentations at once. This is the main API for setting up automatic instrumentation.

  • @opentelemetry/instrumentation-fetch: Automatically instruments fetch() API calls to capture spans for network requests. This is crucial for tracking HTTP requests from your frontend to backend services.

  • @opentelemetry/instrumentation-xml-http-request: Instruments legacy XMLHttpRequest calls for older code or libraries that don't use fetch. This ensures all network requests are captured regardless of the HTTP client used.

  • @opentelemetry/context-zone: Provides context propagation for browser environments using Zone.js. This is essential for maintaining trace context across async operations and ensuring proper distributed tracing.

  • @opentelemetry/resources: Provides resource attributes that identify your service (like service name, version, etc.). This helps distinguish traces from different services in your SigNoz dashboard.

  • @opentelemetry/instrumentation-user-interaction: Automatically instruments user interactions like clicks, inputs, and form submissions. This helps correlate user actions with resulting operations and provides insights into user behavior.

Step 3: Create an instrumentation file

The instrumentation file is required to setup the OTLPTraceExporter and WebTracerProvider which are used to capture traces within your application and export them to your collector.

import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { resourceFromAttributes } from '@opentelemetry/resources';

const exporter = new OTLPTraceExporter({
  url: `https://ingest.<<INGESTION_REGION>>.signoz.cloud:443/v1/traces`,
  headers: {
    'signoz-ingestion-key': <<INGESTION_KEY>>,
  },
});

const provider = new WebTracerProvider({
  resource: resourceFromAttributes({
    'service.name': '<<SERVICE_NAME>>',
  }),
  spanProcessors: [new BatchSpanProcessor(exporter)],
});

provider.register();

registerInstrumentations({
  instrumentations: [
    new FetchInstrumentation({
      // Selects which backend servers are allowed to receive trace headers for linking traces across services.
      // Using /.*/ acts as a wildcard. For safer usage in production, replace with specific domains:
      // e.g. propagateTraceHeaderCorsUrls: [/api\.example\.com/, /my-backend\.internal/]
      propagateTraceHeaderCorsUrls: /.*/,
    }),
    new UserInteractionInstrumentation({
      eventNames: ['click', 'input', 'submit'],
    }),
    new XMLHttpRequestInstrumentation({
      propagateTraceHeaderCorsUrls: /.*/,
    }),
  ],
});
  • Set the <<INGESTION_REGION>> to match your SigNoz Cloud region
  • Replace <<INGESTION_KEY>> with your SigNoz ingestion key
  • <<SERVICE_NAME>> is the name of your service

Going through each of the 3 instrumentations set up:

  • FetchInstrumentation – Automatically instruments fetch() API calls, capturing spans for each network request made via fetch. Supports propagating trace headers to backend services for distributed tracing.

  • XMLHttpRequestInstrumentation – Instruments legacy XHR-based network requests (e.g., older libraries or custom implementations not using fetch). Also adds trace headers to allow backend correlation.

  • UserInteractionInstrumentation – Tracks meaningful user actions like clicks, form inputs, and submissions. This helps correlate user intent with resulting operations like network requests or page changes.
    For example, you can trace that a backend request was triggered by a button click.

Step 4: Importing the instrumentation file

Import the instrumentation file at the top level of your application. This ensures that the OpenTelemetry instrumentation is initialized before any other code runs, allowing it to capture logs from the very beginning of your application's execution.

import './instrument';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>
);

Step 5: Linking frontend traces with backend

To link your frontend and backend traces, you need to instrument your backend to send traces. Check out this document to instrument your backend. Once done, your frontend traces should automatically get linked with your backend traces.

For the purpose of this guide, we have setup a simple Node.js API with instrumentation.

import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    'service.name': '<<SERVICE_NAME>>',
  }),
  traceExporter: new OTLPTraceExporter({
    url: '<<SIGNOZ_COLLECTOR_URL>>/v1/traces',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();
app.post('<<api-endpoint>>', (req, res) => {
  try {
    // Your api logic
  } catch (error) {
    // Your error handling logic
  }
});

Step 6: Viewing Captured Traces in SigNoz

The captured traces can then be viewed in the Traces Explorer.

Viewing Network Calls
Viewing Network Calls
Viewing Distributed Traces linking frontend and backend
Viewing Distributed Traces linking frontend and backend

Attaching additional identifiers to your Traces

You can enrich traces with additional metadata like browser type, user ID etc. to enable real user monitoring (RUM)-like insights.

To do so, you need to write a custom implementation of SpanProcessor which will intercept all your exported spans and attach additional attributes to them.

import { SpanProcessor } from '@opentelemetry/sdk-trace-node';
import { UAParser } from 'ua-parser-js';

export const CONSTANTS = {
  USER_ID: 'user.id',
  BROWSER_NAME: 'browser.name',
  BROWSER_VERSION: 'browser.version',
};

function getBrowserInfo() {
  // You can add your custom browser tracking logic here as well.
  // This example uses the ua-parser-js package.
  const parser = new UAParser();
  const result = parser.getResult();
  return {
    browserName: result.browser.name || '',
    browserVersion: result.browser.version || '',
    userAgent: result.ua || '',
  };
}

function getUserId() {
  // You can add your custom user ID tracking logic here as well.
  // This example uses localStorage.
  const userId = localStorage.getItem('userId');
  return {
    userId: userId || '',
  };
}

const CustomSpanProcessor: SpanProcessor = {
  onStart: span => {
    const userData = getUserId();
    const browserInfo = getBrowserInfo();

    span.setAttribute(CONSTANTS.USER_ID, userData.userId);
    span.setAttribute(CONSTANTS.BROWSER_NAME, browserInfo.browserName);
    span.setAttribute(CONSTANTS.BROWSER_VERSION, browserInfo.browserVersion);
  },
  onEnd: () => Promise.resolve(),
  forceFlush: () => Promise.resolve(),
  shutdown: () => Promise.resolve(),
};

export default CustomSpanProcessor;

Update your instrumentation file to include this processor.

const provider = new WebTracerProvider({
  resource: resourceFromAttributes({
    'service.name': 'text-generator',
  }),
  spanProcessors: [new BatchSpanProcessor(exporter), CustomSpanProcessor],
});

Now every span exported will include these additional contextual attributes.

Distributed Tracing
Distributed Tracing

Manual Instrumentation

If you need to create custom spans for capture activity, you can do so from any handler function in your code like this.

function eventHandler() {
  const span = tracer.startSpan('<<span-name>>');
  try {
    await context.with(trace.setSpan(context.active(), span), async () => {
      // Your data fetching logic
    });
  } catch (err) {
    // Your error fetching log
  } finally {
    // Close the span
    span.end();
  }
};

Demo Application

Check out this full-stack application that demonstrates sending traces from both frontend and backend to SigNoz.

Last updated: August 17, 2025

Edit on GitHub

Was this page helpful?