Skip to content

Setting Up Performance & Error Monitoring

The holiday season is approaching. Traffic is about to spike. Let’s get Sentry instrumented so you can monitor performance, catch errors, and keep your e-commerce site running smoothly when it matters most.

By the end of this module, you will:

  • Configure Error Monitoring for both frontend and backend
  • Set up Sentry Performance Monitoring with Web Vitals tracking (LCP, CLS, FCP, TTFB, INP)
  • Enable Session Replays to debug user interactions visually
  • Enable structured logging to capture console logs alongside traces and errors
  • Implement distributed tracing to track requests across services
  • Integrate React Router v6 for parameterized route tracking
  • Understand sample rates and when to adjust them for production

Sentry is a comprehensive application monitoring platform that helps developers identify, debug, and resolve performance issues and errors before they impact users. Key features for e-commerce applications include:

  • Error Monitoring: Automatic error detection with stack traces and context
  • Performance Monitoring: Track Web Vitals (LCP, CLS, FCP, TTFB, INP) and custom performance metrics
  • Session Replays: Visual recordings of user sessions to understand checkout failures
  • Distributed Tracing: Follow requests from frontend through backend to database
  • Structured Logging: Capture and correlate logs with traces and errors for complete context

Sentry automatically captures five key Web Vitals metrics (LCP, CLS, FCP, TTFB, INP) that measure how users experience your site. You’ll learn what these metrics mean and how to optimize them in the next module. For now, let’s focus on getting Sentry instrumented and data flowing.

  1. Install Sentry’s React SDK

    From the root of your unborked repository:

    Installing the Sentry React SDK
    pnpm add @sentry/react --filter web
  2. Create the instrumentation file with React Router v6 integration

    Create apps/web/src/instrument.ts:

    instrument.ts
    import * as Sentry from '@sentry/react';
    import { useEffect } from 'react';
    import {
    useLocation,
    useNavigationType,
    createRoutesFromChildren,
    matchRoutes,
    } from 'react-router-dom';
    Sentry.init({
    dsn: import.meta.env.VITE_SENTRY_DSN,
    enableLogs: true,
    integrations: [
    // React Router v6 integration for automatic route tracking
    // Captures all 5 Web Vitals: LCP, CLS, FCP, TTFB, INP
    Sentry.reactRouterV6BrowserTracingIntegration({
    useEffect,
    useLocation,
    useNavigationType,
    createRoutesFromChildren,
    matchRoutes,
    // Performance monitoring options
    idleTimeout: 10000, // Wait 10 seconds for additional requests (default: 1000ms)
    _experiments: {
    enableStandaloneLcpSpans: true, // LCP: Largest Contentful Paint
    enableStandaloneClsSpans: true, // CLS: Cumulative Layout Shift (can happen after pageload)
    },
    }),
    Sentry.consoleLoggingIntegration({ levels: ['log', 'warn', 'error'] }),
    // Session Replay for debugging user interactions
    Sentry.replayIntegration({
    maskAllText: false,
    maskAllInputs: true,
    blockAllMedia: false,
    }),
    ],
    // Performance Monitoring
    tracesSampleRate: 1.0, // 100% in dev (reduce to 0.1-0.3 in production)
    // Distributed tracing - connect frontend to backend
    tracePropagationTargets: [import.meta.env.VITE_API_BASE_URL],
    // Session Replay sampling
    replaysSessionSampleRate: 0.1, // 10% of sessions
    replaysOnErrorSampleRate: 1.0, // 100% of errors
    sendDefaultPii: true,
    environment: import.meta.env.MODE,
    });
  3. Create an error boundary component

    Create apps/web/src/components/ErrorBoundary.tsx:

    ErrorBoundary.tsx
    import * as Sentry from '@sentry/react';
    import { useRouteError, isRouteErrorResponse } from 'react-router-dom';
    import { useEffect } from 'react';
    export default function ErrorBoundary() {
    const error = useRouteError();
    useEffect(() => {
    // Send route errors to Sentry
    if (error) {
    Sentry.captureException(error);
    }
    }, [error]);
    if (isRouteErrorResponse(error)) {
    return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
    <div className="text-center">
    <h1 className="text-4xl font-bold text-red-600 mb-4">
    {error.status}
    </h1>
    <p className="text-xl text-gray-600 mb-4">{error.statusText}</p>
    <p className="text-gray-500">{error.data}</p>
    </div>
    </div>
    );
    }
    return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
    <div className="text-center">
    <h1 className="text-2xl font-bold text-red-600 mb-4">
    Something went wrong
    </h1>
    <p className="text-gray-600 mb-4">
    {error instanceof Error ? error.message : 'Unknown error occurred'}
    </p>
    <button
    onClick={() => window.location.reload()}
    className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
    >
    Reload page
    </button>
    </div>
    </div>
    );
    }
  4. Import instrumentation in main.tsx

    Add this import at the very top of apps/web/src/main.tsx:

    main.tsx
    // This MUST be the first import!
    import './instrument';
    import { createRoot } from 'react-dom/client';
    import * as Sentry from '@sentry/react';
    import App from './App.tsx';
    import './index.css';
    createRoot(document.getElementById('root')!, {
    onRecoverableError: Sentry.reactErrorHandler(),
    }).render(<App />);
  5. Wrap your Routes component with Sentry

    Update apps/web/src/App.tsx to wrap your <Routes> component:

    App.tsx
    import { BrowserRouter, Routes, Route } from 'react-router-dom';
    import * as Sentry from '@sentry/react';
    import { CartProvider } from './context/CartContext';
    import { AuthProvider } from './context/AuthContext';
    import ErrorBoundary from './components/ErrorBoundary';
    import Home from './pages/Home';
    import Cart from './pages/Cart';
    import Login from './pages/Login';
    import Register from './pages/Register';
    import ProductDetail from './pages/ProductDetail';
    import Shop from './pages/Shop';
    import Sale from './pages/Sale';
    // Create the Sentry-wrapped Routes component
    const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);
    function App() {
    return (
    <BrowserRouter>
    <AuthProvider>
    <CartProvider>
    <div className="min-h-screen bg-gray-100">
    <SentryRoutes>
    <Route
    path="/"
    element={<Home />}
    errorElement={<ErrorBoundary />}
    />
    <Route
    path="/shop"
    element={<Shop />}
    errorElement={<ErrorBoundary />}
    />
    <Route
    path="/sale"
    element={<Sale />}
    errorElement={<ErrorBoundary />}
    />
    <Route
    path="/cart"
    element={<Cart />}
    errorElement={<ErrorBoundary />}
    />
    <Route
    path="/login"
    element={<Login />}
    errorElement={<ErrorBoundary />}
    />
    <Route
    path="/register"
    element={<Register />}
    errorElement={<ErrorBoundary />}
    />
    <Route
    path="/product/:id"
    element={<ProductDetail />}
    errorElement={<ErrorBoundary />}
    />
    </SentryRoutes>
    </div>
    </CartProvider>
    </AuthProvider>
    </BrowserRouter>
    );
    }
    export default App;
  6. Add Sentry DSN to environment variables

    Open apps/web/.env and add your Sentry DSN (the file already has VITE_API_BASE_URL from the quickstart):

    Adding Sentry DSN to apps/web/.env
    VITE_SENTRY_DSN=<Your Frontend Sentry DSN>
    VITE_API_BASE_URL=http://localhost:3001
  7. Set up sourcemaps with Sentry Wizard

    Sourcemaps help you debug minified production code:

    Setting up sourcemaps with Sentry Wizard
    cd apps/web
    pnpx @sentry/wizard@latest -i sourcemaps

    Follow the prompts:

    • Select Sentry SaaS
    • Choose your organization and frontend project
    • Select Vite as the bundler
    • Select PNPM as package manager
    • Select No for CI/CD

    This creates .env.sentry-build-plugin and updates vite.config.ts automatically.

    The Sentry Vite plugin doesn’t upload source maps in watch-mode/development-mode. You can now upload them to Sentry using the Vite build command:

    Building the frontend and uploading sourcemaps to Sentry
    pnpm build

    This will build your frontend and upload the sourcemaps to Sentry. We can now either use pnpm start or pnpm dev to start the development server. We will stick with pnpm dev for this workshop.

  8. Annotate React Components

    Now that we have a Vite Sentry plugin, we can also extend it to bring in React component names to our traces for easier debugging. Edit vite.config.ts to add the following:

    vite.config.ts
    // ... rest of your vite.config.ts
    sentryVitePlugin({
    org: __YOUR_SENTRY_ORG__,
    project: __YOUR_SENTRY_PROJECT__,
    reactComponentAnnotation: {
    enabled: true,
    },
    }),
    // ... rest of your vite.config.ts
  9. Return to the root directory

    Navigate back to the root of your project:

    Navigating back to the root directory
    cd ../..
Sentry Traces on the frontend
  1. Install Sentry’s Node SDK

    Installing the Sentry Node SDK
    pnpm add @sentry/node --filter api
  2. Create the instrumentation file

    Create apps/api/src/instrument.ts:

    instrument.ts
    import * as Sentry from '@sentry/node';
    Sentry.init({
    dsn: process.env.SENTRY_DSN,
    // Performance Monitoring
    tracesSampleRate: 1.0, // 100% in dev
    // Enable structured logging - captures console.log, console.error, etc.
    enableLogs: true,
    integrations: [
    Sentry.consoleLoggingIntegration({ levels: ['log', 'warn', 'error'] }),
    ],
    environment: process.env.NODE_ENV || 'development',
    });
  3. Import instrumentation in index.ts

    Update apps/api/src/index.ts with the complete instrumentation:

    index.ts
    // This MUST be the first import!
    import './instrument';
    import express from 'express';
    import * as Sentry from '@sentry/node';
    import cors from 'cors';
    import dotenv from 'dotenv';
    import routes from './routes';
    import { Request, Response, NextFunction } from 'express';
    dotenv.config();
    const app = express();
    const PORT = 3001;
    const FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:4173';
    const corsOptions = {
    origin: [FRONTEND_URL],
    methods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],
    allowedHeaders: [
    'Content-Type',
    'Authorization',
    'X-API-Key',
    'sentry-trace',
    'baggage',
    ],
    credentials: true,
    };
    app.use(cors(corsOptions));
    app.use(express.json());
    app.use((req: Request, res: Response, next: NextFunction) => {
    console.log(`${req.method} ${req.url} - Request received`);
    next();
    });
    // Routes
    app.use('/api', routes);
    // Add Sentry error handler AFTER all routes
    Sentry.setupExpressErrorHandler(app);
    // Custom error handler goes AFTER Sentry's error handler
    app.use((err: Error, req: Request, res: Response, _next: NextFunction) => {
    console.error('Unhandled error occurred:', err.message, err.stack);
    res.status(500).json({
    error: 'Something broke!',
    message:
    process.env.NODE_ENV === 'development'
    ? err.message
    : 'Internal Server Error',
    });
    });
    const startServer = async () => {
    try {
    console.log('🚀 Starting server...');
    // Start server
    console.log('About to call app.listen on port', PORT);
    app.listen(PORT, () => {
    console.log('✅ Server is running on port', PORT);
    console.log(`🔗 Allowed frontend origin: ${FRONTEND_URL}`);
    });
    } catch (startupError: any) {
    console.error(
    '❌ Failed to start server:',
    startupError.message,
    startupError.stack
    );
    process.exit(1);
    }
    };
    startServer();
  4. Add Sentry DSN to environment variables

    Open apps/api/.env and add your Sentry DSN (the file already has DATABASE_URL from the quickstart):

    Adding Sentry DSN to apps/api/.env
    SENTRY_DSN=<Your Backend Sentry DSN>
    DATABASE_URL=postgresql://...
  5. Test your setup

    Your server should be running already, but if not, start it with:

    Starting the development servers
    cd ../.. # Back to root
    pnpm dev

    Open http://localhost:4173 and browse around. You should see transactions flowing into both Sentry projects.

Sentry Traces on the backend

Before moving on, let’s confirm everything is working:

  • Frontend transactions appear in Sentry Performance view with route names (e.g., /products/:id)
  • Backend transactions appear in Sentry Performance view
  • Session Replays are being recorded (check Replays tab in frontend project)
  • Console logs appear in Sentry traces and replays (check the console tab in any trace)
  • Error boundary is set up and routing errors are captured
  • Both frontend and backend are sending data to Sentry

Having issues? Check that:

  • Environment variables are set correctly in both .env files
  • instrument.ts is imported first in both main.tsx and index.ts
  • Your dev servers are running (pnpm dev from root)

Next up: We’ll identify and fix Web Vitals issues like slow LCP, layout shifts, and render-blocking requests that hurt your conversion rates.