Monitoring and Error Tracking in Makerkit
Set up error tracking and performance monitoring in your Next.js Supabase SaaS app with Sentry, PostHog, or SigNoz.
Steps to configure monitoring
Learn how to configure monitoring in the Next.js Supabase Starter Kit.
Understanding the Monitoring Architecture
Makerkit's monitoring system uses a provider-based architecture that lets you swap monitoring services without changing your application code. The system lives in the @kit/monitoring package and handles:
- Error tracking: Capture client-side and server-side exceptions
- Performance monitoring: Track server response times via OpenTelemetry instrumentation
- User identification: Associate errors with specific users for debugging
The architecture follows a registry pattern. When you set NEXT_PUBLIC_MONITORING_PROVIDER, Makerkit loads the appropriate service implementation at runtime:
MonitoringProvider (React context) │ ▼ Registry lookup │ ▼┌───────┴───────┐│ sentry ││ posthog ││ signoz │└───────────────┘This means your components interact with a consistent MonitoringService interface regardless of which provider you choose.
Supported Monitoring Providers
Makerkit provides first-class support for these monitoring providers:
| Provider | Error Tracking | Performance | Self-Hostable | Notes |
|---|---|---|---|---|
| Sentry | Yes | Yes | Yes | Built-in, recommended for most apps |
| PostHog | Yes | No | Yes | Plugin, doubles as analytics |
| SigNoz | Yes | Yes | Yes | Plugin, OpenTelemetry-native |
Sentry is included out of the box. PostHog and SigNoz require installing plugins via the Makerkit CLI.
Custom providers
You can add support for any monitoring service by implementing the MonitoringService interface and registering it in the provider registry. See Adding a custom monitoring provider below.
Configuring Your Monitoring Provider
Set these environment variables to enable monitoring:
# Required: Choose your provider (sentry, posthog, or signoz)NEXT_PUBLIC_MONITORING_PROVIDER=sentry# Provider-specific configuration# See the individual provider docs for required variablesThe NEXT_PUBLIC_MONITORING_PROVIDER variable determines which service handles your errors. Leave it empty to disable monitoring entirely (errors still log to console in development).
What Gets Monitored Automatically
Once configured, Makerkit captures errors without additional code:
Client-side exceptions
The MonitoringProvider component wraps your app and captures uncaught exceptions in React components. This includes:
- Runtime errors in components
- Unhandled promise rejections
- Errors thrown during rendering
Server-side exceptions
Next.js 15+ includes an instrumentation hook that captures server errors automatically. Makerkit hooks into this via instrumentation.ts:
import { type Instrumentation } from 'next';export const onRequestError: Instrumentation.onRequestError = async ( err, request, context,) => { const { getServerMonitoringService } = await import('@kit/monitoring/server'); const service = await getServerMonitoringService(); await service.ready(); await service.captureException( err as Error, {}, { path: request.path, headers: request.headers, method: request.method, routePath: context.routePath, }, );};This captures errors from Server Components, Server Actions, Route Handlers, and Middleware.
Manually Capturing Exceptions
For expected errors (like validation failures or API errors), capture them explicitly:
In Server Actions or Route Handlers
import { getServerMonitoringService } from '@kit/monitoring/server';export async function createProject(data: FormData) { try { // ... your logic } catch (error) { const monitoring = await getServerMonitoringService(); await monitoring.ready(); monitoring.captureException(error, { extra: { action: 'createProject', userId: user.id, }, }); throw error; // Re-throw or handle as needed }}In React Components
Use the useMonitoring hook for client-side error capture:
'use client';import { useMonitoring } from '@kit/monitoring/hooks';export function DataLoader() { const monitoring = useMonitoring(); async function loadData() { try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error(`Failed to load data: ${response.status}`); } return response.json(); } catch (error) { monitoring.captureException(error, { extra: { component: 'DataLoader' }, }); throw error; } } // ...}The useCaptureException Hook
For error boundaries or components that receive errors as props:
'use client';import { useCaptureException } from '@kit/monitoring/hooks';export function ErrorDisplay({ error }: { error: Error }) { // Automatically captures the error when the component mounts useCaptureException(error); return ( <div> <h2>Something went wrong</h2> <p>{error.message}</p> </div> );}Identifying Users in Error Reports
Associate errors with users to debug issues faster. Makerkit's monitoring providers support user identification:
const monitoring = useMonitoring();// After user signs inmonitoring.identifyUser({ id: user.id, email: user.email, // Additional fields depend on your provider});Makerkit automatically identifies users when they sign in if you've configured the analytics/events system. The user.signedIn event triggers user identification in both analytics and monitoring.
Adding a Custom Monitoring Provider
To add a provider not included in Makerkit:
1. Implement the MonitoringService interface
import { MonitoringService } from '@kit/monitoring-core';export class MyProviderMonitoringService implements MonitoringService { private readyPromise: Promise<void>; private readyResolver?: () => void; constructor() { this.readyPromise = new Promise((resolve) => { this.readyResolver = resolve; }); this.initialize(); } async ready() { return this.readyPromise; } captureException(error: Error, extra?: Record<string, unknown>) { // Send to your monitoring service myProviderSDK.captureException(error, { extra }); } captureEvent(event: string, extra?: Record<string, unknown>) { myProviderSDK.captureEvent(event, extra); } identifyUser(user: { id: string }) { myProviderSDK.setUser(user); } private initialize() { // Initialize your SDK myProviderSDK.init({ dsn: process.env.MY_PROVIDER_DSN }); this.readyResolver?.(); }}2. Register the provider
Add your provider to the monitoring registries:
const MONITORING_PROVIDERS = [ 'sentry', 'my-provider', // Add your provider '',] as const;serverMonitoringRegistry.register('my-provider', async () => { const { MyProviderMonitoringService } = await import('@kit/my-provider'); return new MyProviderMonitoringService();});monitoringProviderRegistry.register('my-provider', async () => { const { MyProviderProvider } = await import('@kit/my-provider/provider'); return { default: function MyProviderWrapper({ children }: React.PropsWithChildren) { return <MyProviderProvider>{children}</MyProviderProvider>; }, };});Telegram notifications
We wrote a tutorial showing how to add Telegram notifications for error monitoring: Send SaaS errors to Telegram.
Best Practices
Do capture context with errors
// Good: Includes debugging contextmonitoring.captureException(error, { extra: { userId: user.id, accountId: account.id, action: 'updateBillingPlan', planId: newPlanId, },});// Less useful: No contextmonitoring.captureException(error);Don't capture expected validation errors
// Avoid: This clutters your error dashboardif (!isValidEmail(email)) { monitoring.captureException(new Error('Invalid email')); return { error: 'Invalid email' };}// Better: Only capture unexpected failurestry { await sendEmail(email);} catch (error) { monitoring.captureException(error, { extra: { email: maskEmail(email) }, });}Next Steps
Choose a monitoring provider and follow its setup guide:
- Configure Sentry (recommended for most apps)
- Configure PostHog (if you already use PostHog for analytics)
- Configure SigNoz (self-hosted, OpenTelemetry-native)