Skip to main content
TechCal includes comprehensive monitoring and telemetry to track errors, performance, user behavior, and recommendation quality.

Sentry Error Tracking

TechCal uses Sentry for error tracking and performance monitoring.

Configuration

Sentry is configured in three contexts:
sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  enabled: process.env.NODE_ENV !== 'development',
  tracesSampleRate: 0.1, // Sample 10% of transactions
  debug: process.env.SENTRY_DEBUG === 'true',
  replaysSessionSampleRate: 0,
  replaysOnErrorSampleRate: 0,
});
Server-side Sentry captures:
  • API route errors
  • Server component errors
  • Background job failures

Environment Variables

# Sentry DSN (public, safe to expose)
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/project-id

# Auth token for uploading source maps (build-time only)
SENTRY_AUTH_TOKEN=your_sentry_auth_token

# Enable/disable Sentry
SENTRY_ENABLED=true

# Debug mode (verbose logging)
SENTRY_DEBUG=false
Sentry is automatically disabled in development mode to save memory. Set SENTRY_ENABLED=true to override.
TechCal logs breadcrumbs for debugging context:

Scoring Analytics

src/services/analyticsService.ts
import * as Sentry from '@sentry/nextjs';

export class AnalyticsService {
  static logScoringDebug(payload: {
    eventId: string;
    version: string;
    triggers: string[];
    components: Record<string, number>;
  }) {
    Sentry.addBreadcrumb({
      category: 'scoring-analytics',
      level: 'info',
      message: 'Scoring triggers',
      data: payload,
    });
  }
}
Usage:
AnalyticsService.logScoringDebug({
  eventId: 'evt_123',
  version: 'v2.3.0',
  triggers: ['goal_alignment', 'skill_match'],
  components: {
    goalScore: 0.85,
    skillScore: 0.72,
    overallScore: 0.79,
  },
});

Profile Prompt Events

AnalyticsService.logProfilePromptEvent('prompt_shown', {
  userId: 'user_123',
  trigger: 'incomplete_profile',
  fieldsNeeded: ['skills', 'goals'],
});

Error Context

Add custom context to errors:
import * as Sentry from '@sentry/nextjs';

Sentry.setContext('user', {
  id: user.id,
  role: user.current_role,
  onboardingComplete: user.onboarding_complete,
});

Sentry.setContext('recommendation', {
  strategy: 'advanced',
  version: 'v2.3.0',
  eventCount: 150,
});

Recommendation Monitoring

Track recommendation performance and quality with RecommendationMonitoringService.

Quick Metrics

Get real-time recommendation metrics:
import { RecommendationMonitoringService } from '@/services/recommendationMonitoringService';

const metrics = await RecommendationMonitoringService.getQuickMetrics(
  supabase,
  userId,
  7 // Last 7 days
);

// {
//   totalEvents: 150,
//   totalClicks: 23,
//   ctr: 0.153, // 15.3% click-through rate
//   avgScore: 0.72,
//   topTriggers: ['goal_alignment', 'skill_match', 'interest_boost']
// }

Score Range Analysis

Analyze CTR by score ranges:
const scoreRanges = await RecommendationMonitoringService.analyzeScoreRanges(
  supabase,
  userId,
  30 // Last 30 days
);

// [
//   {
//     range: '0.8-1.0',
//     minScore: 0.8,
//     maxScore: 1.0,
//     totalEvents: 45,
//     totalClicks: 12,
//     clickThroughRate: 0.267, // 26.7%
//     avgScore: 0.89,
//     avgConfidence: 0.92,
//   },
//   { range: '0.6-0.8', ... },
//   ...
// ]

Trigger Analysis

Identify which scoring triggers drive engagement:
const triggers = await RecommendationMonitoringService.analyzeTriggers(
  supabase,
  userId,
  30
);

// [
//   {
//     trigger: 'goal_alignment',
//     eventCount: 78,
//     totalClicks: 21,
//     clickThroughRate: 0.269,
//     avgScore: 0.81,
//     scoreRangeDistribution: {
//       '0.8-1.0': 34,
//       '0.6-0.8': 44,
//     },
//   },
//   ...
// ]

Monitoring Summary

Comprehensive analytics dashboard data:
const summary = await RecommendationMonitoringService.getMonitoringSummary(
  supabase,
  userId,
  30
);

// {
//   dateRange: { from: '2026-02-04', to: '2026-03-04' },
//   totalEvents: 450,
//   totalInteractions: 87,
//   overallCTR: 0.193,
//   scoreRanges: [...],
//   topTriggers: [...],
//   outliers: [...],
//   recommendations: [
//     'High CTR in 0.8-1.0 range suggests threshold is effective',
//     'Consider boosting events with "goal_alignment" trigger',
//   ],
//   behavioralBoostStats: { ... },
// }

Monitoring Configuration

Configure thresholds in src/config/monitoringConstants.ts:
export const MONITORING_CONFIG = {
  scoreRanges: [
    { min: 0.8, max: 1.0, label: '0.8-1.0' },
    { min: 0.6, max: 0.8, label: '0.6-0.8' },
    { min: 0.4, max: 0.6, label: '0.4-0.6' },
    { min: 0.0, max: 0.4, label: '0.0-0.4' },
  ],
  outlierThresholds: {
    highPerformance: 0.3, // CTR > 30%
    lowPerformance: 0.05, // CTR < 5%
  },
};

Analytics & Telemetry

TechCal logs sanitized analytics events to Supabase.

Telemetry Service

src/utils/supabase/telemetry.ts
import { logTelemetryEvent } from '@/utils/supabase/telemetry';

// Log event interaction
await logTelemetryEvent(supabase, userId, {
  event_type: 'event_click',
  event_id: 'evt_123',
  algorithm_version: 'v2.3.0',
  score: 0.85,
  triggers: ['goal_alignment', 'skill_match'],
});

// Log filter change
await logTelemetryEvent(supabase, userId, {
  event_type: 'filter_applied',
  filters: {
    event_type: ['conference', 'workshop'],
    budget: 'free',
  },
});
Respect user telemetry preferences:
const { data: profile } = await supabase
  .from('profiles')
  .select('telemetry_consent')
  .eq('id', userId)
  .single();

if (profile.telemetry_consent) {
  await logTelemetryEvent(supabase, userId, event);
}

PostHog Integration

TechCal supports PostHog for product analytics:
src/lib/posthog-server.ts
import { PostHog } from 'posthog-node';

const posthog = new PostHog(
  process.env.NEXT_PUBLIC_POSTHOG_KEY!,
  {
    host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
  }
);

// Track event
posthog.capture({
  distinctId: userId,
  event: 'recommendation_shown',
  properties: {
    eventId: 'evt_123',
    score: 0.85,
    version: 'v2.3.0',
  },
});
PostHog is optional. Set NEXT_PUBLIC_POSTHOG_KEY to enable.

Rate Limiting

TechCal uses Upstash Rate Limit to protect high-traffic APIs.

Configuration

Rate limiting with Vercel KV:
src/app/api/events/filtered/route.ts
import { Ratelimit } from '@upstash/ratelimit';
import { kv } from '@vercel/kv';

const ratelimit = new Ratelimit({
  redis: kv,
  limiter: Ratelimit.slidingWindow(100, '1 m'), // 100 requests per minute
});

export async function GET(request: Request) {
  const userId = request.headers.get('x-user-id');
  
  const { success, limit, remaining } = await ratelimit.limit(
    `api_filtered_${userId}`
  );
  
  if (!success) {
    return new Response('Rate limit exceeded', {
      status: 429,
      headers: {
        'X-RateLimit-Limit': limit.toString(),
        'X-RateLimit-Remaining': remaining.toString(),
      },
    });
  }
  
  // Handle request...
}

Rate Limit Strategies

// 100 requests per minute
Ratelimit.slidingWindow(100, '1 m')

Client-Side Handling

Gracefully handle rate limits:
const response = await fetch('/api/events/filtered');

if (response.status === 429) {
  const remaining = response.headers.get('X-RateLimit-Remaining');
  const limit = response.headers.get('X-RateLimit-Limit');
  
  showNotification({
    type: 'warning',
    message: `Rate limit exceeded. Try again in a moment.`,
  });
  
  return;
}

Environment Variables

# Vercel KV (automatically populated when linked)
KV_REST_API_URL=https://your-kv.kv.vercel-storage.com
KV_REST_API_TOKEN=your_token

# Alternative: Upstash Redis
UPSTASH_REDIS_REST_URL=https://your-redis.upstash.io
UPSTASH_REDIS_REST_TOKEN=your_token

Caching Strategy

TechCal uses multiple caching layers:

Vercel KV Cache

Cache expensive operations:
import { kv } from '@vercel/kv';

const cacheKey = `user_recommendations_${userId}`;
const cached = await kv.get(cacheKey);

if (cached) {
  return cached;
}

const recommendations = await computeRecommendations(userId);

// Cache for 5 minutes
await kv.set(cacheKey, recommendations, { ex: 300 });

return recommendations;

React Query Cache

Client-side data caching:
import { useQuery } from '@tanstack/react-query';

const { data, isLoading } = useQuery({
  queryKey: ['events', filters],
  queryFn: () => fetchFilteredEvents(filters),
  staleTime: 5 * 60 * 1000, // 5 minutes
  cacheTime: 10 * 60 * 1000, // 10 minutes
});

Performance Monitoring

API Response Times

Log slow API calls:
const start = Date.now();
const result = await expensiveOperation();
const duration = Date.now() - start;

if (duration > 1000) {
  console.warn(`Slow operation: ${duration}ms`);
  Sentry.captureMessage(`Slow API call: ${duration}ms`, 'warning');
}

Database Query Performance

Monitor Supabase queries:
const start = performance.now();
const { data, error } = await supabase
  .from('events')
  .select('*')
  .gte('start_date', today);
const duration = performance.now() - start;

if (duration > 500) {
  Sentry.addBreadcrumb({
    category: 'database',
    message: 'Slow query',
    data: { duration, table: 'events' },
  });
}

Monitoring Dashboard

Access monitoring data:

Sentry Dashboard

  • Errors - Real-time error tracking
  • Performance - Transaction traces, slow queries
  • Releases - Deploy tracking, regression detection
  • Alerts - Email/Slack notifications

Vercel Analytics

  • Core Web Vitals - LCP, FID, CLS
  • Page Performance - Load times, TTFB
  • User Metrics - Unique visitors, page views

Custom Analytics

Build custom dashboards with recommendation monitoring:
src/app/admin/analytics/page.tsx
import { RecommendationMonitoringService } from '@/services/recommendationMonitoringService';

export default async function AnalyticsPage() {
  const summary = await RecommendationMonitoringService.getMonitoringSummary(
    supabase,
    null, // All users
    30
  );
  
  return (
    <Dashboard>
      <MetricCard title="Overall CTR" value={summary.overallCTR} />
      <ScoreRangeChart data={summary.scoreRanges} />
      <TriggerTable data={summary.topTriggers} />
    </Dashboard>
  );
}

Alerting

Sentry Alerts

Configure in Sentry dashboard:
  • Error spikes - Alert when error rate increases
  • New issues - Notify on first occurrence
  • Performance degradation - Alert on slow transactions

Custom Alerts

Implement custom monitoring:
const metrics = await RecommendationMonitoringService.getQuickMetrics(
  supabase,
  null,
  1
);

if (metrics.ctr < 0.05) {
  // CTR dropped below 5%
  await sendSlackAlert({
    channel: '#alerts',
    message: `⚠️ Recommendation CTR dropped to ${metrics.ctr * 100}%`,
  });
}

Next Steps

Deployment

Deploy with monitoring configured

Environment Variables

Configure monitoring environment variables

Build docs developers (and LLMs) love