Monitoring

Comprehensive guide for monitoring ShipKit applications using Sentry, Vercel Analytics, and custom monitoring solutions

Monitoring

This guide covers monitoring solutions for ShipKit applications, including application performance monitoring, error tracking, health checks, and analytics.

Application Performance Monitoring (APM)

Vercel Speed Insights

// app/layout.tsx
import { SpeedInsights } from '@vercel/speed-insights/next'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <SpeedInsights />
      </body>
    </html>
  )
}

Performance Metrics

// src/lib/monitoring/performance.ts
import { type NextWebVitalsMetric } from 'next/app'

export function reportWebVitals(metric: NextWebVitalsMetric) {
  const { id, name, label, value } = metric

  // Core Web Vitals
  if (name === 'FCP') {
    console.log('First Contentful Paint:', value)
  }
  if (name === 'LCP') {
    console.log('Largest Contentful Paint:', value)
  }
  if (name === 'CLS') {
    console.log('Cumulative Layout Shift:', value)
  }
  if (name === 'FID') {
    console.log('First Input Delay:', value)
  }
  if (name === 'TTFB') {
    console.log('Time to First Byte:', value)
  }

  // Send to analytics
  sendToAnalytics({
    metric: name,
    id,
    value: Math.round(value * 1000),
    label: label === 'web-vital' ? 'Web Vital' : 'Custom',
  })
}

function sendToAnalytics(params: {
  metric: string
  id: string
  value: number
  label: string
}) {
  const body = JSON.stringify(params)
  const url = '/api/analytics'

  // Use `navigator.sendBeacon()` if available
  if (navigator.sendBeacon) {
    navigator.sendBeacon(url, body)
  } else {
    fetch(url, { body, method: 'POST', keepalive: true })
  }
}

Error Tracking

Sentry Integration

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

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  environment: process.env.NEXT_PUBLIC_APP_ENV,

  // Performance Monitoring
  tracesSampleRate: 1.0,

  // Session Replay
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,

  // Enable automatic instrumentation
  integrations: [
    new Sentry.BrowserTracing({
      tracePropagationTargets: ['localhost', /^https:\/\/[^/]*\.your-domain\.com/],
    }),
    new Sentry.Replay(),
  ],
})

Error Boundaries

// src/components/error-boundary.tsx
'use client'

import * as Sentry from '@sentry/nextjs'
import { useEffect } from 'react'

interface Props {
  error: Error & { digest?: string }
  reset: () => void
}

export default function ErrorBoundary({ error, reset }: Props) {
  useEffect(() => {
    // Log error to Sentry
    Sentry.captureException(error)
  }, [error])

  return (
    <div className="flex h-[50vh] flex-col items-center justify-center gap-4">
      <h2 className="text-xl font-semibold">Something went wrong!</h2>
      <button
        onClick={reset}
        className="rounded-md bg-primary px-4 py-2 text-white"
      >
        Try again
      </button>
    </div>
  )
}

Custom Error Logger

// src/lib/monitoring/logger.ts
type LogLevel = 'info' | 'warn' | 'error'

interface LogEntry {
  level: LogLevel
  message: string
  timestamp: string
  context?: Record<string, unknown>
}

class Logger {
  private static instance: Logger
  private logs: LogEntry[] = []

  private constructor() {}

  static getInstance(): Logger {
    if (!Logger.instance) {
      Logger.instance = new Logger()
    }
    return Logger.instance
  }

  log(level: LogLevel, message: string, context?: Record<string, unknown>) {
    const entry: LogEntry = {
      level,
      message,
      timestamp: new Date().toISOString(),
      context,
    }

    this.logs.push(entry)
    this.persist(entry)

    if (level === 'error') {
      Sentry.captureMessage(message, {
        level: Sentry.Severity.Error,
        extra: context,
      })
    }
  }

  private persist(entry: LogEntry) {
    // Send to backend logging service
    fetch('/api/logs', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(entry),
    }).catch(console.error)
  }

  getLogs(): LogEntry[] {
    return this.logs
  }
}

export const logger = Logger.getInstance()

Health Checks

API Health Check

// src/app/api/health/route.ts
import { NextResponse } from 'next/server'
import { db } from '@/server/db'
import { redis } from '@/server/redis'
import { env } from '@/env.mjs'

export async function GET() {
  try {
    const checks = await Promise.allSettled([
      // Database check
      db.$queryRaw`SELECT 1`,

      // Redis check
      redis.ping(),

      // External API checks
      fetch(`${env.NEXT_PUBLIC_APP_URL}/api`),
      fetch('https://api.openai.com/v1/models', {
        headers: { Authorization: `Bearer ${env.OPENAI_API_KEY}` },
      }),
    ])

    const results = {
      database: checks[0].status === 'fulfilled',
      redis: checks[1].status === 'fulfilled',
      api: checks[2].status === 'fulfilled',
      openai: checks[3].status === 'fulfilled',
    }

    const isHealthy = Object.values(results).every(Boolean)

    return NextResponse.json(
      {
        status: isHealthy ? 'healthy' : 'unhealthy',
        timestamp: new Date().toISOString(),
        checks: results,
      },
      { status: isHealthy ? 200 : 503 }
    )
  } catch (error) {
    return NextResponse.json(
      {
        status: 'unhealthy',
        timestamp: new Date().toISOString(),
        error: error.message,
      },
      { status: 503 }
    )
  }
}

Automated Health Monitoring

// src/lib/monitoring/health-monitor.ts
interface HealthCheck {
  name: string
  check: () => Promise<boolean>
}

export class HealthMonitor {
  private checks: HealthCheck[] = []
  private interval: NodeJS.Timeout | null = null

  addCheck(name: string, check: () => Promise<boolean>) {
    this.checks.push({ name, check })
  }

  async runChecks() {
    const results = await Promise.all(
      this.checks.map(async ({ name, check }) => {
        try {
          const isHealthy = await check()
          return { name, status: isHealthy ? 'healthy' : 'unhealthy' }
        } catch (error) {
          return { name, status: 'unhealthy', error: error.message }
        }
      })
    )

    const unhealthyChecks = results.filter(r => r.status === 'unhealthy')
    if (unhealthyChecks.length > 0) {
      logger.error('Health checks failed', { checks: unhealthyChecks })
    }

    return results
  }

  startMonitoring(intervalMs = 60000) {
    if (this.interval) {
      clearInterval(this.interval)
    }

    this.interval = setInterval(() => {
      this.runChecks().catch(error => {
        logger.error('Failed to run health checks', { error })
      })
    }, intervalMs)
  }

  stopMonitoring() {
    if (this.interval) {
      clearInterval(this.interval)
      this.interval = null
    }
  }
}

Analytics and Metrics

Vercel Analytics

// src/components/providers/analytics-provider.tsx
'use client'

import { Analytics } from '@vercel/analytics/react'
import { SpeedInsights } from '@vercel/speed-insights/next'

export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <Analytics />
      <SpeedInsights />
    </>
  )
}

Custom Analytics Events

// src/lib/monitoring/analytics.ts
type EventName =
  | 'page_view'
  | 'button_click'
  | 'form_submit'
  | 'error'
  | 'api_call'

interface AnalyticsEvent {
  name: EventName
  properties?: Record<string, unknown>
  timestamp?: string
}

class Analytics {
  private static instance: Analytics
  private events: AnalyticsEvent[] = []

  private constructor() {}

  static getInstance(): Analytics {
    if (!Analytics.instance) {
      Analytics.instance = new Analytics()
    }
    return Analytics.instance
  }

  track(name: EventName, properties?: Record<string, unknown>) {
    const event: AnalyticsEvent = {
      name,
      properties,
      timestamp: new Date().toISOString(),
    }

    this.events.push(event)
    this.send(event)
  }

  private async send(event: AnalyticsEvent) {
    try {
      await fetch('/api/analytics', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(event),
      })
    } catch (error) {
      logger.error('Failed to send analytics event', { error, event })
    }
  }

  getEvents(): AnalyticsEvent[] {
    return this.events
  }
}

export const analytics = Analytics.getInstance()

Monitoring Dashboard

// src/app/(app)/(admin)/monitoring/page.tsx
import { Suspense } from 'react'
import { HealthStatus } from './health-status'
import { ErrorRate } from './error-rate'
import { PerformanceMetrics } from './performance-metrics'
import { APILatency } from './api-latency'
import { UserActivity } from './user-activity'

export default function MonitoringDashboard() {
  return (
    <div className="grid gap-6 p-6 md:grid-cols-2">
      <Suspense fallback={<div>Loading health status...</div>}>
        <HealthStatus />
      </Suspense>

      <Suspense fallback={<div>Loading error rate...</div>}>
        <ErrorRate />
      </Suspense>

      <Suspense fallback={<div>Loading performance metrics...</div>}>
        <PerformanceMetrics />
      </Suspense>

      <Suspense fallback={<div>Loading API latency...</div>}>
        <APILatency />
      </Suspense>

      <Suspense fallback={<div>Loading user activity...</div>}>
        <UserActivity />
      </Suspense>
    </div>
  )
}

Best Practices

  1. Performance Monitoring

    • Monitor Core Web Vitals
    • Track custom performance metrics
    • Set up alerts for performance degradation
    • Use real user monitoring (RUM)
  2. Error Tracking

    • Implement error boundaries
    • Set up source maps
    • Track error rates and trends
    • Monitor error impact on users
  3. Health Checks

    • Monitor critical dependencies
    • Set up automated health checks
    • Configure alerting thresholds
    • Track system uptime
  4. Analytics

    • Track key business metrics
    • Monitor user behavior
    • Analyze performance impact
    • Set up custom events

Monitoring Checklist

  1. Setup

    • [ ] Configure Sentry
    • [ ] Set up Vercel Analytics
    • [ ] Implement health checks
    • [ ] Configure error tracking
  2. Implementation

    • [ ] Add performance monitoring
    • [ ] Implement error boundaries
    • [ ] Set up custom analytics
    • [ ] Configure logging
  3. Validation

    • [ ] Test error reporting
    • [ ] Verify health checks
    • [ ] Validate analytics events
    • [ ] Check performance metrics
  4. Maintenance

    • [ ] Review error logs
    • [ ] Monitor performance trends
    • [ ] Update alert thresholds
    • [ ] Analyze user metrics