Skip to main content
Sentry provides real-time error tracking and performance monitoring for both the frontend and backend of PC Fix. It helps identify, debug, and resolve issues quickly.

Overview

PC Fix uses Sentry to monitor:
  • Backend Errors: API exceptions, database errors, server crashes
  • Frontend Errors: JavaScript errors, React component failures, Astro SSR errors
  • Performance: Request tracing, slow queries, response times

Configuration

1

Create a Sentry Account

Sign up for a free account at sentry.io. The free tier includes 5,000 errors per month.
2

Create Two Projects

Create two projects in your Sentry organization:
  • Backend Project: Node.js/Express
  • Frontend Project: Astro
Copy the DSN (Data Source Name) from each project.
3

Set Environment Variables

Add the DSNs to your environment files:Backend (packages/api/.env):
SENTRY_DSN=https://[email protected]/project-id
NODE_ENV=production
Frontend (packages/web/.env):
PUBLIC_SENTRY_DSN=https://[email protected]/project-id
The frontend DSN is prefixed with PUBLIC_ because it’s exposed to the browser. Use separate DSNs for frontend and backend to organize errors properly.

Backend Setup (Node.js/Express)

Sentry is initialized at the very beginning of packages/api/src/server.ts:
import 'dotenv/config';
import * as Sentry from '@sentry/node';

// Initialize Sentry first, before any other code
if (process.env.SENTRY_DSN) {
  Sentry.init({
    dsn: process.env.SENTRY_DSN,
    environment: process.env.NODE_ENV || 'development',
    tracesSampleRate: 1.0, // Capture 100% of transactions
  });
}

// Rest of your server code...
import express from 'express';
const app = express();
Sentry is only initialized if SENTRY_DSN is set, allowing you to disable monitoring in local development.

Automatic Error Capture

With Sentry initialized, all uncaught exceptions and unhandled promise rejections are automatically captured:
// This error will be automatically sent to Sentry
app.get('/api/users/:id', async (req, res) => {
  const user = await prisma.user.findUniqueOrThrow({
    where: { id: parseInt(req.params.id) }
  });
  res.json(user);
});

Manual Error Capture

You can manually capture errors with additional context:
import * as Sentry from '@sentry/node';

try {
  await processPayment(order);
} catch (error) {
  Sentry.captureException(error, {
    tags: {
      section: 'payments',
      order_id: order.id
    },
    extra: {
      order_details: order,
      payment_method: order.paymentMethod
    }
  });
  throw error;
}

Adding User Context

Attach user information to errors for better debugging:
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.nombre
});

Frontend Setup (Astro)

Sentry is conditionally integrated in packages/web/astro.config.mjs:
import { defineConfig } from 'astro/config';
import sentry from '@sentry/astro';

export default defineConfig({
  integrations: [
    // ... other integrations
    
    // Conditionally enable Sentry
    ...(process.env.PUBLIC_SENTRY_DSN ? [sentry({
      dsn: process.env.PUBLIC_SENTRY_DSN,
      sourceMapsUploadOptions: {
        enabled: false, // Set to true in production
      },
    })] : [])
  ],
});

Client-Side Error Capture

Frontend errors are automatically captured:
// Uncaught exceptions are automatically sent to Sentry
const data = await fetch('/api/products').then(res => res.json());

Manual Frontend Capture

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

try {
  await submitForm(formData);
} catch (error) {
  Sentry.captureException(error, {
    tags: { form: 'checkout' },
    extra: { formData }
  });
  showErrorMessage('Failed to submit form');
}

Error Context

Add contextual information to help debug issues:
Sentry.setTag('page', 'checkout');
Sentry.setTag('payment_method', 'mercadopago');

Performance Monitoring

Sentry automatically tracks performance metrics:

Backend Performance

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

const transaction = Sentry.startTransaction({
  op: 'process-order',
  name: 'Process Order Pipeline'
});

try {
  const span1 = transaction.startChild({ op: 'validate' });
  await validateOrder(order);
  span1.finish();

  const span2 = transaction.startChild({ op: 'payment' });
  await processPayment(order);
  span2.finish();

  transaction.finish();
} catch (error) {
  transaction.setStatus('internal_error');
  transaction.finish();
  throw error;
}

Frontend Performance

const transaction = Sentry.startTransaction({
  name: 'Load Product Page',
  op: 'page-load'
});

const span = transaction.startChild({
  op: 'fetch',
  description: 'Fetch product data'
});

await fetchProduct(id);
span.finish();

transaction.finish();

Environment Configuration

SENTRY_DSN
string
Backend Sentry DSN (kept secret on the server)
PUBLIC_SENTRY_DSN
string
Frontend Sentry DSN (exposed to the browser)
NODE_ENV
string
default:"development"
Environment name (development, staging, production)
tracesSampleRate
number
Percentage of transactions to monitor (1.0 = 100%)

Filtering Errors

Exclude known errors or noisy issues:
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  beforeSend(event, hint) {
    // Ignore specific errors
    if (event.exception?.values?.[0]?.type === 'ChunkLoadError') {
      return null;
    }
    
    // Ignore errors from bots
    if (event.request?.headers?.['user-agent']?.includes('bot')) {
      return null;
    }
    
    return event;
  }
});

Release Tracking

Track which version of your code is running:
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  release: process.env.VERCEL_GIT_COMMIT_SHA || 'dev',
  environment: process.env.NODE_ENV
});

Integration with Error Handler

Sentry works seamlessly with your existing error middleware:
globalErrorHandler.ts
import * as Sentry from '@sentry/node';

export const globalErrorHandler = (err, req, res, next) => {
  // Log to Sentry
  Sentry.captureException(err, {
    tags: {
      path: req.path,
      method: req.method
    },
    user: req.user ? {
      id: req.user.id,
      email: req.user.email
    } : undefined
  });

  // Send response to client
  res.status(err.statusCode || 500).json({
    success: false,
    message: err.message
  });
};

Dashboard Features

Issues

View and triage errors grouped by root cause

Performance

Monitor transaction speeds and identify bottlenecks

Releases

Track errors by deployment version

Alerts

Get notified of new errors via email, Slack, or webhooks

Best Practices

Create separate Sentry projects for frontend and backend to keep errors organized and easier to filter.
Always include relevant context like user ID, order ID, or page name to make debugging easier.
Configure alerts for critical errors so you can respond quickly to production issues.
Use a lower tracesSampleRate (e.g., 0.1 = 10%) in high-traffic production environments to reduce costs.
Don’t send errors to Sentry during local development. Only initialize when SENTRY_DSN is set.

Troubleshooting

  • Verify your DSN is correct
  • Check that SENTRY_DSN (backend) or PUBLIC_SENTRY_DSN (frontend) is set
  • Ensure Sentry.init() is called before any other code
  • Check your network/firewall isn’t blocking sentry.io
  • Use beforeSend to filter out noisy errors
  • Increase the issue alert threshold in Sentry settings
  • Fix the underlying bugs causing repeated errors
Enable source map upload in production:
sentry({
  dsn: process.env.PUBLIC_SENTRY_DSN,
  sourceMapsUploadOptions: {
    enabled: true,
    authToken: process.env.SENTRY_AUTH_TOKEN
  }
})

Security Considerations

  • Never log sensitive information (passwords, credit cards, tokens)
  • Use beforeSend to scrub PII (personally identifiable information)
  • Keep backend DSN secret (use SENTRY_DSN without PUBLIC_ prefix)
  • Frontend DSN can be public (it’s exposed in browser code)

Build docs developers (and LLMs) love