SigNoz Cloud - This page is relevant for SigNoz Cloud editions.
Self-Host - This page is relevant for self-hosted SigNoz editions.

Web Vitals Monitoring with OpenTelemetry Metrics

Overview

This documentation provides a step-by-step guide to setting up web vitals monitoring with OpenTelemetry metrics on SigNoz.

📝 Note

Already sending frontend metrics?

If you've followed the Sending Metrics from your frontend application guide, you can skip Steps 1 and 2 below as your instrumentation file is already set up. Jump straight to Step 3 to add web vitals capture to your existing setup.

Prerequisites

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

Setup

Step 1: Install dependencies

npm install web-vitals
npm install @opentelemetry/api
npm install @opentelemetry/exporter-metrics-otlp-http
npm install @opentelemetry/sdk-metrics
npm install @opentelemetry/resources
Read more about the dependencies
  • web-vitals: Core library for measuring and reporting web vitals metrics (LCP, INP, CLS, TTFB, FCP)
  • @opentelemetry/api: Provides the core OpenTelemetry API for creating metrics and meters
  • @opentelemetry/exporter-metrics-otlp-http: Exports metrics to OpenTelemetry Protocol (OTLP) over HTTP
  • @opentelemetry/sdk-metrics: Provides the OpenTelemetry metrics SDK implementation
  • @opentelemetry/resources: Defines service metadata and attributes for your application

Step 2: Create an instrumentation file

The instrumentation file is required to setup the MeterProvider which is used to create custom metrics.

instrumentation.ts
import {
  MeterProvider,
  PeriodicExportingMetricReader,
} from '@opentelemetry/sdk-metrics';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { metrics } from '@opentelemetry/api';

// Define your resource, e.g., service name, environment.
const resource = resourceFromAttributes({
  'service.name': '<SERVICE_NAME>',
});

// Create a metric reader with OTLP exporter configured to send metrics to a local collector.
const metricReader = new PeriodicExportingMetricReader({
  exporter: new OTLPMetricExporter({
    // For the self-hosted version, please use the collector url instead.
    url: `https://ingest.<INGESTION_REGION>.signoz.cloud:443/v1/metrics`,
    headers: {
      // Optional for the self-hosted version
      'signoz-ingestion-key': '<INGESTION_KEY>',
    },
  }),
  exportIntervalMillis: 10000, // Export metrics every 10 seconds.
});

// Initialize a MeterProvider with the above configurations.
const myServiceMeterProvider = new MeterProvider({
  resource,
  readers: [metricReader],
});

// Set the initialized MeterProvider as global to enable metric collection across the app.
metrics.setGlobalMeterProvider(myServiceMeterProvider);
  • 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
📝 Note

For self-hosted deployments, add the following CORS configuration to your OpenTelemetry Collector config file to allow browser requests from your frontend domain. Also, update the endpoint and remove the ingestion key header as shown in Cloud → Self-Hosted.

config.yaml
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: ['*']

This configures the OTel Collector to export traces to SigNoz Cloud using the OTLP/HTTP protocol. Read more about OTel Collector configuration here.

Step 3: Capture and export Web Vitals

Use the web-vitals library to capture key performance metrics like CLS, INP, LCP, TTFB, and FCP. These are captured through callback functions that you define and exported as metrics.

web-vitals.ts
import { metrics } from '@opentelemetry/api';
import { onCLS, onFCP, onINP, onLCP, onTTFB, type Metric } from 'web-vitals';

const meter = metrics.getMeter('web-vitals');
const lcp = meter.createHistogram('lcp');
const cls = meter.createObservableGauge('cls');
const inp = meter.createHistogram('inp');
const ttfb = meter.createHistogram('ttfb');
const fcp = meter.createHistogram('fcp');

function sendToAnalytics(metric: Metric) {
  switch (metric.name) {
    case 'LCP': {
      lcp.record(metric.value);
      break;
    }
    case 'CLS': {
      cls.addCallback(result => {
        result.observe(metric.value);
      });
      break;
    }
    case 'INP': {
      inp.record(metric.value);
      break;
    }
    case 'TTFB': {
      ttfb.record(metric.value);

      break;
    }
    case 'FCP': {
      fcp.record(metric.value);
      break;
    }
    default: {
      console.log('unexpected metric name');
    }
  }
}

onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);
onFCP(sendToAnalytics);

Validate

The captured web vitals can be viewed in the Metrics Explorer.

Web Vitals
Web Vitals

Attaching additional identifiers to your Web Vitals

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

web-vitals.ts
import { metrics } from '@opentelemetry/api';
import { UAParser } from 'ua-parser-js';
import { onCLS, onFCP, onINP, onLCP, onTTFB, type Metric } from 'web-vitals';

const pageUrl = window.location.pathname
  .replace(/\?.*$/, '') // remove query params
  .replace(/\/\d+/g, '/:id'); // normalize numeric IDs

export const CONSTANTS = {
  USER_ID: 'user.id',
  USER_AGENT_NAME: 'user_agent.name',
  PAGE_URL: 'url.path_template',
};

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 result.browser.name || '';
}

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 || '';
}

const customAttributes = {
  [CONSTANTS.USER_ID]: getUserId(),
  [CONSTANTS.USER_AGENT_NAME]: getBrowserInfo(),
  [CONSTANTS.PAGE_URL]: pageUrl,
};

const meter = metrics.getMeter('web-vitals');
const lcp = meter.createHistogram('lcp');
const cls = meter.createObservableGauge('cls');
const inp = meter.createHistogram('inp');
const ttfb = meter.createHistogram('ttfb');
const fcp = meter.createHistogram('fcp');

function sendToAnalytics(metric: Metric) {
  switch (metric.name) {
    case 'LCP': {
      lcp.record(metric.value, customAttributes);
      break;
    }
    case 'CLS': {
      cls.addCallback(result => {
        result.observe(metric.value, customAttributes);
      });
      break;
    }
    case 'INP': {
      inp.record(metric.value, customAttributes);
      break;
    }
    case 'TTFB': {
      ttfb.record(metric.value, customAttributes);

      break;
    }
    case 'FCP': {
      fcp.record(metric.value, customAttributes);
      break;
    }
    default: {
      console.log('unexpected metric name');
    }
  }
}

onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onTTFB(sendToAnalytics);
onFCP(sendToAnalytics);

Setup Dashboard and alerts

Core Web Vitals Monitoring in SigNoz
Core Web Vitals Monitoring in SigNoz

SigNoz charts support thresholds with color coding. For example, the graph below shows three thresholds for good, needs improvement, and critical levels.

Thresholds in SigNoz
Thresholds provide a visual cue about your application's performance

Out of Box Dashboard

You can import our out-of-the-box Dashboard for web vitals monitoring

Demo Application

We have set up a Demo React application that demonstrates monitoring Core Web Vitals with metrics using OpenTelemetry and sending them to SigNoz.

Next Steps

Last updated: November 20, 2025

Edit on GitHub

Was this page helpful?