Skip to main content
Performance monitoring helps identify slow operations and optimize user experience while minimizing overhead on your application.

Enabling Performance Monitoring

Browser Applications

import * as Sentry from '@sentry/browser';

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    Sentry.browserTracingIntegration(),
  ],
  tracesSampleRate: 0.1, // Capture 10% of transactions
});

Node.js Applications

import * as Sentry from '@sentry/node';

// MUST be first import
Sentry.init({
  dsn: '__DSN__',
  tracesSampleRate: 0.1,
});

// Now import other modules
import express from 'express';
For Node.js SDK v8+, you MUST initialize Sentry before any other imports for OpenTelemetry instrumentation to work.

Instrumentation

Automatic Instrumentation

The SDK automatically instruments: Browser:
  • Page load transactions
  • Navigation transactions
  • Fetch/XHR requests
  • User interactions (clicks, form inputs)
  • Web Vitals (LCP, FID, CLS, INP, TTFB)
Node.js:
  • HTTP/HTTPS requests
  • Express.js routes
  • Database queries (Prisma, MongoDB, MySQL, PostgreSQL)
  • GraphQL operations
  • Redis operations
// Browser
Sentry.init({
  dsn: '__DSN__',
  integrations: [
    Sentry.browserTracingIntegration({
      // Customize instrumentation
      traceFetch: true,
      traceXHR: true,
      enableLongAnimationFrame: true,
    }),
  ],
});

// Node.js - integrations are auto-enabled
Sentry.init({
  dsn: '__DSN__',
  tracesSampleRate: 0.1,
  // These are enabled by default:
  // - httpIntegration()
  // - expressIntegration()
  // - prismaIntegration()
  // - etc.
});

Manual Instrumentation

Create custom spans for specific operations:
import { startSpan } from '@sentry/browser';

const result = await startSpan(
  {
    name: 'complex-calculation',
    op: 'function',
    attributes: {
      'calc.type': 'fibonacci',
      'calc.input': n,
    },
  },
  async (span) => {
    const result = await fibonacci(n);
    span.setAttribute('calc.result', result);
    return result;
  }
);

Nested Spans

Track sub-operations:
await startSpan(
  { name: 'process-order', op: 'task' },
  async (parentSpan) => {
    await startSpan(
      { name: 'validate-order', op: 'validation' },
      async () => {
        await validateOrder(order);
      }
    );
    
    await startSpan(
      { name: 'charge-payment', op: 'payment' },
      async () => {
        await chargePayment(order);
      }
    );
    
    await startSpan(
      { name: 'create-shipment', op: 'logistics' },
      async () => {
        await createShipment(order);
      }
    );
  }
);

Controlling Span Creation

HTTP Instrumentation

Control which requests create spans:
import { httpIntegration } from '@sentry/node';

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    httpIntegration({
      tracing: {
        // Don't create spans for certain URLs
        shouldCreateSpanForRequest: (url) => {
          return !url.includes('/health') && 
                 !url.includes('/metrics');
        },
      },
    }),
  ],
});

Browser Request Instrumentation

import { browserTracingIntegration } from '@sentry/browser';

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    browserTracingIntegration({
      shouldCreateSpanForRequest: (url) => {
        // Don't trace analytics or ads
        return !url.match(/google-analytics|doubleclick/);
      },
    }),
  ],
});

Trace Propagation

Control distributed tracing headers:
Sentry.init({
  dsn: '__DSN__',
  // Propagate to same origin by default
  // Customize for specific targets:
  tracePropagationTargets: [
    'localhost',
    /^https:\/\/api\.myapp\.com/,
    /^\/api\//,  // Same-origin API routes
  ],
});
In v8+, trace propagation defaults to same-origin requests. Only set tracePropagationTargets for cross-origin distributed tracing.

Web Vitals

Monitor Core Web Vitals automatically:
Sentry.init({
  dsn: '__DSN__',
  integrations: [
    Sentry.browserTracingIntegration(),
  ],
});

// Captured automatically:
// - Largest Contentful Paint (LCP)
// - First Input Delay (FID) - deprecated, replaced by INP in v10+
// - Cumulative Layout Shift (CLS)
// - Interaction to Next Paint (INP)
// - Time to First Byte (TTFB)

Custom Measurements

Add custom performance measurements:
import { setMeasurement, startSpan } from '@sentry/browser';

startSpan({ name: 'page-load' }, () => {
  // Custom measurement
  setMeasurement('custom.metric', 123, 'millisecond');
  
  // Multiple measurements
  setMeasurement('bundle.size', 450, 'kilobyte');
  setMeasurement('api.calls', 5, 'none');
});

Transaction Naming

Browser Route Naming

import { browserTracingIntegration } from '@sentry/react';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    browserTracingIntegration(),
  ],
});

// React Router integration handles naming automatically

Custom Transaction Names

import { getActiveSpan, getRootSpan } from '@sentry/browser';

// Update transaction name dynamically
const activeSpan = getActiveSpan();
if (activeSpan) {
  const rootSpan = getRootSpan(activeSpan);
  rootSpan.updateName('Custom Transaction Name');
}

Node.js Route Naming

import express from 'express';

const app = express();

app.get('/users/:id', (req, res) => {
  // Automatically named: GET /users/:id
  res.json({ id: req.params.id });
});

Sampling Strategies

Prioritize Important Transactions

Sentry.init({
  dsn: '__DSN__',
  tracesSampler: (samplingContext) => {
    // Always sample checkout flows
    if (samplingContext.name?.includes('/checkout')) {
      return 1.0;
    }
    
    // Sample API calls moderately
    if (samplingContext.name?.startsWith('/api/')) {
      return 0.2;
    }
    
    // Don't sample health checks
    if (samplingContext.name === '/health') {
      return 0;
    }
    
    // Default sampling
    return 0.05;
  },
});

Environment-Based Sampling

const isProd = process.env.NODE_ENV === 'production';

Sentry.init({
  dsn: '__DSN__',
  tracesSampleRate: isProd ? 0.05 : 1.0,
});

Performance Overhead Reduction

Disable Unnecessary Integrations

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    // Only enable what you need
    Sentry.browserTracingIntegration({
      // Disable interaction tracking if not needed
      traceFetch: true,
      traceXHR: true,
    }),
  ],
});

Limit Breadcrumb Collection

import { breadcrumbsIntegration } from '@sentry/browser';

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    breadcrumbsIntegration({
      console: false, // Disable console breadcrumbs
      dom: true,
      fetch: true,
      history: true,
      sentry: false,
      xhr: true,
    }),
  ],
  maxBreadcrumbs: 50, // Default is 100
});

Selective Instrumentation

import { httpIntegration } from '@sentry/node';

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    httpIntegration({
      // Disable if you don't need HTTP spans
      spans: false,
    }),
  ],
});

Framework-Specific Optimizations

React

import * as Sentry from '@sentry/react';
import {
  createRoutesFromChildren,
  matchRoutes,
  useLocation,
  useNavigationType,
} from 'react-router-dom';

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    Sentry.reactRouterV6BrowserTracingIntegration({
      useEffect: React.useEffect,
      useLocation,
      useNavigationType,
      createRoutesFromChildren,
      matchRoutes,
    }),
  ],
  tracesSampleRate: 0.1,
});

Next.js

Next.js SDK handles instrumentation automatically:
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: '__DSN__',
  tracesSampleRate: 0.1,
  // Browser tracing is automatic
});

// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: '__DSN__',
  tracesSampleRate: 0.1,
  // Server instrumentation is automatic
});

Vue

import * as Sentry from '@sentry/vue';

Sentry.init({
  app,
  dsn: '__DSN__',
  integrations: [
    Sentry.browserTracingIntegration({
      router, // Pass Vue Router instance
    }),
  ],
  tracesSampleRate: 0.1,
});

Database Query Instrumentation

Prisma

import { prismaIntegration } from '@sentry/node';

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    prismaIntegration(),
  ],
});

// Queries are automatically instrumented
const users = await prisma.user.findMany();

MongoDB (Mongoose)

import { mongooseIntegration } from '@sentry/node';

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    mongooseIntegration(),
  ],
});

Best Practices

High-traffic applications should use lower sample rates:
const sampleRate = process.env.NODE_ENV === 'production' ? 0.05 : 1.0;

Sentry.init({
  dsn: '__DSN__',
  tracesSampleRate: sampleRate,
});
These create noise without value:
tracesSampler: (context) => {
  if (context.name?.match(/\/(health|metrics|ping)/)) {
    return 0;
  }
  return 0.1;
}
Set appropriate op values for better organization:
startSpan({ name: 'db-query', op: 'db.query' }, () => {});
startSpan({ name: 'api-call', op: 'http.client' }, () => {});
startSpan({ name: 'render', op: 'ui.render' }, () => {});
Too many spans can increase overhead. Focus on meaningful operations:
// ❌ Too granular
for (const item of items) {
  startSpan({ name: 'process-item' }, () => processItem(item));
}

// ✅ Better
startSpan({ name: 'process-items' }, () => {
  items.forEach(processItem);
});
Add context to help debug performance issues:
startSpan(
  {
    name: 'fetch-user-data',
    op: 'http.client',
    attributes: {
      'http.method': 'GET',
      'http.url': '/api/users/123',
      'user.id': '123',
    },
  },
  () => {}
);

Monitoring Performance Impact

Check SDK Overhead

const start = performance.now();

// Your operation
await myOperation();

const duration = performance.now() - start;
console.log('Operation duration:', duration);

Profile Mode

Enable profiling for deeper insights:
import { browserProfilingIntegration } from '@sentry/browser';

Sentry.init({
  dsn: '__DSN__',
  integrations: [
    browserProfilingIntegration(),
  ],
  tracesSampleRate: 0.1,
  profilesSampleRate: 0.1, // Relative to traces
});

Complete Example

import * as Sentry from '@sentry/browser';
import { startSpan } from '@sentry/browser';

const environment = process.env.NODE_ENV;
const isProd = environment === 'production';

Sentry.init({
  dsn: '__DSN__',
  environment,
  
  integrations: [
    Sentry.browserTracingIntegration({
      // Customize what's traced
      traceFetch: true,
      traceXHR: true,
      enableLongAnimationFrame: true,
      
      // Filter requests
      shouldCreateSpanForRequest: (url) => {
        return !url.match(/(\.png|\.jpg|analytics|ads)/);
      },
    }),
  ],
  
  // Sampling strategy
  tracesSampler: (context) => {
    // Never sample health checks
    if (context.name === '/health') return 0;
    
    // Always sample checkout
    if (context.name?.includes('/checkout')) return 1.0;
    
    // Production: low rate
    // Development: high rate
    return isProd ? 0.05 : 1.0;
  },
  
  // Propagate to your API
  tracePropagationTargets: [
    'localhost',
    /^https:\/\/api\.myapp\.com/,
  ],
  
  // Reduce breadcrumbs
  maxBreadcrumbs: 50,
});

// Manual instrumentation example
export async function complexOperation(userId) {
  return await startSpan(
    {
      name: 'complex-operation',
      op: 'task',
      attributes: { 'user.id': userId },
    },
    async (span) => {
      // Step 1
      await startSpan(
        { name: 'fetch-user', op: 'http.client' },
        async () => {
          const user = await fetchUser(userId);
          span.setAttribute('user.role', user.role);
          return user;
        }
      );
      
      // Step 2
      await startSpan(
        { name: 'process-data', op: 'function' },
        async () => {
          return await processUserData(user);
        }
      );
    }
  );
}

Next Steps

Bundle Size Optimization

Reduce SDK bundle size impact

Source Maps

Configure source maps for production

Build docs developers (and LLMs) love