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.

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:

ProviderError TrackingPerformanceSelf-HostableNotes
SentryYesYesYesBuilt-in, recommended for most apps
PostHogYesNoYesPlugin, doubles as analytics
SigNozYesYesYesPlugin, OpenTelemetry-native

Sentry is included out of the box. PostHog and SigNoz require installing plugins via the Makerkit CLI.

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 variables

The 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 exposes an instrumentation hook that fires for server errors. Makerkit hooks into it via apps/web/instrumentation.ts, which is a thin shim that forwards to the configured provider:

import { type Instrumentation } from 'next';
export async function register() {
const { registerMonitoringInstrumentation } = await import(
'@kit/monitoring/instrumentation'
);
await registerMonitoringInstrumentation();
}
export const onRequestError: Instrumentation.onRequestError = async (
error,
request,
context,
) => {
const { onRequestError: handler } = await import(
'@kit/monitoring/instrumentation'
);
return handler(error, request, context);
};

This captures errors from Server Components, Server Actions, Route Handlers, and Middleware.

Client-side exceptions

The browser counterpart lives at apps/web/instrumentation-client.ts, a Next.js convention file that runs before hydration:

import { registerClientMonitoringInstrumentation } from '@kit/monitoring/instrumentation-client';
registerClientMonitoringInstrumentation();

registerClientMonitoringInstrumentation installs provider-agnostic window.onerror and unhandledrejection listeners, then lazily loads the configured provider in parallel with hydration. Errors that fire before the provider chunk is ready are buffered and replayed.

Avoiding duplicate reports

A server error returned to the client carries a digest property. Because onRequestError already captured it, your error.tsx boundary should skip the client report when digest is present — pass null to useCaptureException:

useCaptureException(error.digest ? null : error);

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, {
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, {
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 in
monitoring.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

Adding a provider involves five registration points — see the Custom Provider guide for the full walkthrough. The short version:

  1. Add the provider name to the MONITORING_PROVIDERS enum in get-monitoring-provider.ts.
  2. Register a server MonitoringService in services/get-server-monitoring-service.ts.
  3. Register a React MonitoringProvider component in components/provider.tsx.
  4. Register a server instrumentation entry in instrumentation.ts — both register (to init the SDK) and onRequestError (to forward Next.js request errors) are required.
  5. Register a client instrumentation entry in instrumentation-client.tsinit and captureException.

Best Practices

Do capture context with errors

// Good: Includes debugging context
monitoring.captureException(error, {
userId: user.id,
accountId: account.id,
action: 'updateBillingPlan',
planId: newPlanId,
});
// Less useful: No context
monitoring.captureException(error);

Don't capture expected validation errors

// Avoid: This clutters your error dashboard
if (!isValidEmail(email)) {
monitoring.captureException(new Error('Invalid email'));
return { error: 'Invalid email' };
}
// Better: Only capture unexpected failures
try {
await sendEmail(email);
} catch (error) {
monitoring.captureException(error, {
extra: { email: maskEmail(email) },
});
}

Next Steps

Choose a monitoring provider and follow its setup guide: