Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
348 lines
10 KiB
Plaintext
348 lines
10 KiB
Plaintext
---
|
|
title: "Monitoring and Error Tracking in Makerkit"
|
|
status: "published"
|
|
label: "How Monitoring Works"
|
|
order: 0
|
|
description: "Set up error tracking and performance monitoring in your Next.js Supabase SaaS app with Sentry, PostHog, or SigNoz."
|
|
---
|
|
|
|
{% sequence title="Steps to configure monitoring" description="Learn how to configure monitoring in the Next.js Supabase Starter Kit." %}
|
|
|
|
[Understanding the monitoring architecture](#understanding-the-monitoring-architecture)
|
|
|
|
[Supported monitoring providers](#supported-monitoring-providers)
|
|
|
|
[Configuring your monitoring provider](#configuring-your-monitoring-provider)
|
|
|
|
[What gets monitored automatically](#what-gets-monitored-automatically)
|
|
|
|
[Manually capturing exceptions](#manually-capturing-exceptions)
|
|
|
|
[Identifying users in error reports](#identifying-users-in-error-reports)
|
|
|
|
{% /sequence %}
|
|
|
|
## 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](/docs/next-supabase-turbo/monitoring/sentry) | Yes | Yes | Yes | Built-in, recommended for most apps |
|
|
| [PostHog](/docs/next-supabase-turbo/monitoring/posthog) | Yes | No | Yes | Plugin, doubles as analytics |
|
|
| [SigNoz](/docs/next-supabase-turbo/monitoring/signoz) | Yes | Yes | Yes | Plugin, OpenTelemetry-native |
|
|
|
|
**Sentry** is included out of the box. PostHog and SigNoz require installing plugins via the Makerkit CLI.
|
|
|
|
{% alert type="default" title="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](#adding-a-custom-monitoring-provider) below.
|
|
{% /alert %}
|
|
|
|
## Configuring Your Monitoring Provider
|
|
|
|
Set these environment variables to enable monitoring:
|
|
|
|
```bash title=".env.local"
|
|
# 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 15+ includes an instrumentation hook that captures server errors automatically. Makerkit hooks into this via `instrumentation.ts`:
|
|
|
|
```typescript title="apps/web/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
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```tsx
|
|
'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:
|
|
|
|
```tsx
|
|
'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:
|
|
|
|
```typescript
|
|
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
|
|
|
|
To add a provider not included in Makerkit:
|
|
|
|
### 1. Implement the MonitoringService interface
|
|
|
|
```typescript title="packages/monitoring/my-provider/src/my-provider.service.ts"
|
|
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:
|
|
|
|
```typescript title="packages/monitoring/api/src/get-monitoring-provider.ts"
|
|
const MONITORING_PROVIDERS = [
|
|
'sentry',
|
|
'my-provider', // Add your provider
|
|
'',
|
|
] as const;
|
|
```
|
|
|
|
```typescript title="packages/monitoring/api/src/services/get-server-monitoring-service.ts"
|
|
serverMonitoringRegistry.register('my-provider', async () => {
|
|
const { MyProviderMonitoringService } = await import('@kit/my-provider');
|
|
return new MyProviderMonitoringService();
|
|
});
|
|
```
|
|
|
|
```typescript title="packages/monitoring/api/src/components/provider.tsx"
|
|
monitoringProviderRegistry.register('my-provider', async () => {
|
|
const { MyProviderProvider } = await import('@kit/my-provider/provider');
|
|
|
|
return {
|
|
default: function MyProviderWrapper({ children }: React.PropsWithChildren) {
|
|
return <MyProviderProvider>{children}</MyProviderProvider>;
|
|
},
|
|
};
|
|
});
|
|
```
|
|
|
|
{% alert type="default" title="Telegram notifications" %}
|
|
We wrote a tutorial showing how to add Telegram notifications for error monitoring: [Send SaaS errors to Telegram](/blog/tutorials/telegram-saas-error-monitoring).
|
|
{% /alert %}
|
|
|
|
## Best Practices
|
|
|
|
### Do capture context with errors
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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:
|
|
|
|
- [Configure Sentry](/docs/next-supabase-turbo/monitoring/sentry) (recommended for most apps)
|
|
- [Configure PostHog](/docs/next-supabase-turbo/monitoring/posthog) (if you already use PostHog for analytics)
|
|
- [Configure SigNoz](/docs/next-supabase-turbo/monitoring/signoz) (self-hosted, OpenTelemetry-native)
|