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
Performance Monitoring
Monitor Core Web Vitals
Track custom performance metrics
Set up alerts for performance degradation
Use real user monitoring (RUM)
Error Tracking
Implement error boundaries
Set up source maps
Track error rates and trends
Monitor error impact on users
Health Checks
Monitor critical dependencies
Set up automated health checks
Configure alerting thresholds
Track system uptime
Analytics
Track key business metrics
Monitor user behavior
Analyze performance impact
Set up custom events
Monitoring Checklist
Setup
[ ] Configure Sentry
[ ] Set up Vercel Analytics
[ ] Implement health checks
[ ] Configure error tracking
Implementation
[ ] Add performance monitoring
[ ] Implement error boundaries
[ ] Set up custom analytics
[ ] Configure logging
Validation
[ ] Test error reporting
[ ] Verify health checks
[ ] Validate analytics events
[ ] Check performance metrics
Maintenance
[ ] Review error logs
[ ] Monitor performance trends
[ ] Update alert thresholds
[ ] Analyze user metrics