Next.js Supabase V3 (#463)
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
This commit is contained in:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
356
docs/monitoring/capturing-errors.mdoc
Normal file
356
docs/monitoring/capturing-errors.mdoc
Normal file
@@ -0,0 +1,356 @@
|
||||
---
|
||||
status: "published"
|
||||
title: 'Manual Error Capturing'
|
||||
label: 'Capturing Errors'
|
||||
description: 'Learn how to manually capture errors and exceptions in both client and server code using Makerkit monitoring hooks and services.'
|
||||
order: 2
|
||||
---
|
||||
|
||||
While Makerkit automatically captures unhandled errors, you often need to capture errors manually in try-catch blocks, form submissions, or API calls. This guide shows you how to capture errors programmatically.
|
||||
|
||||
{% sequence title="Error Capturing Guide" description="Manually capture errors in your application" %}
|
||||
|
||||
[Client-Side Error Capturing](#client-side-error-capturing)
|
||||
|
||||
[Server-Side Error Capturing](#server-side-error-capturing)
|
||||
|
||||
[Adding Context to Errors](#adding-context-to-errors)
|
||||
|
||||
[Identifying Users](#identifying-users)
|
||||
|
||||
{% /sequence %}
|
||||
|
||||
## Client-Side Error Capturing
|
||||
|
||||
### Using the useCaptureException Hook
|
||||
|
||||
The simplest way to capture errors in React components is the `useCaptureException` hook:
|
||||
|
||||
```typescript {% title="components/error-boundary.tsx" %}
|
||||
'use client';
|
||||
|
||||
import { useCaptureException } from '@kit/monitoring/hooks';
|
||||
|
||||
export default function ErrorPage({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
// Automatically captures the error when the component mounts
|
||||
useCaptureException(error);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong</h2>
|
||||
<button onClick={reset}>Try again</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
This hook captures the error once when the component mounts. It's ideal for error boundary pages.
|
||||
|
||||
### Using the useMonitoring Hook
|
||||
|
||||
For more control, use the `useMonitoring` hook to access the monitoring service directly:
|
||||
|
||||
```typescript {% title="components/form.tsx" %}
|
||||
'use client';
|
||||
|
||||
import { useMonitoring } from '@kit/monitoring/hooks';
|
||||
|
||||
export function ContactForm() {
|
||||
const monitoring = useMonitoring();
|
||||
|
||||
const handleSubmit = async (formData: FormData) => {
|
||||
try {
|
||||
await submitForm(formData);
|
||||
} catch (error) {
|
||||
// Capture the error with context
|
||||
monitoring.captureException(error as Error, {
|
||||
formData: Object.fromEntries(formData),
|
||||
action: 'contact_form_submit',
|
||||
});
|
||||
|
||||
// Show user-friendly error
|
||||
toast.error('Failed to submit form');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form action={handleSubmit}>
|
||||
{/* form fields */}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Capturing Events
|
||||
|
||||
Track custom monitoring events (not errors) using `captureEvent`:
|
||||
|
||||
```typescript {% title="Example: Tracking important actions" %}
|
||||
'use client';
|
||||
|
||||
import { useMonitoring } from '@kit/monitoring/hooks';
|
||||
|
||||
export function DangerZone() {
|
||||
const monitoring = useMonitoring();
|
||||
|
||||
const handleDeleteAccount = async () => {
|
||||
// Track the action before attempting
|
||||
monitoring.captureEvent('account_deletion_attempted', {
|
||||
userId: user.id,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
try {
|
||||
await deleteAccount();
|
||||
} catch (error) {
|
||||
monitoring.captureException(error as Error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button onClick={handleDeleteAccount}>
|
||||
Delete Account
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Server-Side Error Capturing
|
||||
|
||||
### In Server Actions
|
||||
|
||||
```typescript {% title="lib/actions/create-project.ts" %}
|
||||
'use server';
|
||||
|
||||
import { getServerMonitoringService } from '@kit/monitoring/server';
|
||||
|
||||
export async function createProject(formData: FormData) {
|
||||
const monitoring = await getServerMonitoringService();
|
||||
await monitoring.ready();
|
||||
|
||||
try {
|
||||
const project = await db.project.create({
|
||||
data: {
|
||||
name: formData.get('name') as string,
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true, project };
|
||||
} catch (error) {
|
||||
await monitoring.captureException(error as Error, {
|
||||
action: 'createProject',
|
||||
formData: Object.fromEntries(formData),
|
||||
});
|
||||
|
||||
return { success: false, error: 'Failed to create project' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### In API Routes
|
||||
|
||||
```typescript {% title="app/api/webhook/route.ts" %}
|
||||
import { getServerMonitoringService } from '@kit/monitoring/server';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const monitoring = await getServerMonitoringService();
|
||||
await monitoring.ready();
|
||||
|
||||
try {
|
||||
const data = await request.json();
|
||||
await processWebhook(data);
|
||||
|
||||
return Response.json({ received: true });
|
||||
} catch (error) {
|
||||
await monitoring.captureException(
|
||||
error as Error,
|
||||
{ webhook: 'stripe' },
|
||||
{
|
||||
path: request.url,
|
||||
method: 'POST',
|
||||
}
|
||||
);
|
||||
|
||||
return Response.json(
|
||||
{ error: 'Webhook processing failed' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Adding Context to Errors
|
||||
|
||||
The `captureException` method accepts two optional parameters for adding context:
|
||||
|
||||
```typescript
|
||||
captureException(
|
||||
error: Error,
|
||||
extra?: Record<string, unknown>, // Additional data
|
||||
config?: Record<string, unknown> // Provider-specific config
|
||||
)
|
||||
```
|
||||
|
||||
### Extra Data
|
||||
|
||||
Add any relevant information that helps debug the error:
|
||||
|
||||
```typescript {% title="Example: Rich error context" %}
|
||||
monitoring.captureException(error, {
|
||||
// User context
|
||||
userId: user.id,
|
||||
userEmail: user.email,
|
||||
userPlan: user.subscription.plan,
|
||||
|
||||
// Action context
|
||||
action: 'checkout',
|
||||
productId: product.id,
|
||||
quantity: cart.items.length,
|
||||
|
||||
// Environment context
|
||||
feature_flags: getEnabledFlags(),
|
||||
app_version: process.env.APP_VERSION,
|
||||
});
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Pass provider-specific configuration:
|
||||
|
||||
```typescript {% title="Example: Sentry-specific config" %}
|
||||
monitoring.captureException(
|
||||
error,
|
||||
{ userId: user.id },
|
||||
{
|
||||
// Sentry-specific options
|
||||
level: 'error',
|
||||
tags: {
|
||||
component: 'checkout',
|
||||
flow: 'payment',
|
||||
},
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Identifying Users
|
||||
|
||||
Associate errors with users to track issues per user:
|
||||
|
||||
```typescript {% title="Example: Identify user after login" %}
|
||||
'use client';
|
||||
|
||||
import { useMonitoring } from '@kit/monitoring/hooks';
|
||||
|
||||
export function useIdentifyUser(user: { id: string; email: string }) {
|
||||
const monitoring = useMonitoring();
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
monitoring.identifyUser({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
});
|
||||
}
|
||||
}, [user, monitoring]);
|
||||
}
|
||||
```
|
||||
|
||||
Once identified, all subsequent errors from that session are associated with the user in your monitoring dashboard.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Capture at Boundaries
|
||||
|
||||
Capture errors at the boundaries of your application:
|
||||
|
||||
- Form submissions
|
||||
- API calls
|
||||
- Third-party integrations
|
||||
- File uploads
|
||||
- Payment processing
|
||||
|
||||
```typescript {% title="Pattern: Error boundary function" %}
|
||||
async function withErrorCapture<T>(
|
||||
fn: () => Promise<T>,
|
||||
context: Record<string, unknown>
|
||||
): Promise<T | null> {
|
||||
const monitoring = await getServerMonitoringService();
|
||||
await monitoring.ready();
|
||||
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
await monitoring.captureException(error as Error, context);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const result = await withErrorCapture(
|
||||
() => processPayment(paymentData),
|
||||
{ action: 'processPayment', amount: paymentData.amount }
|
||||
);
|
||||
```
|
||||
|
||||
### Don't Over-Capture
|
||||
|
||||
Not every error needs to be captured:
|
||||
|
||||
```typescript {% title="Example: Selective capturing" %}
|
||||
try {
|
||||
await fetchUserData();
|
||||
} catch (error) {
|
||||
if (error instanceof NetworkError) {
|
||||
// Expected error, handle gracefully
|
||||
return fallbackData;
|
||||
}
|
||||
|
||||
if (error instanceof AuthError) {
|
||||
// Expected error, redirect to login
|
||||
redirect('/login');
|
||||
}
|
||||
|
||||
// Unexpected error, capture it
|
||||
monitoring.captureException(error as Error);
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
|
||||
### Include Actionable Context
|
||||
|
||||
Add context that helps you fix the issue:
|
||||
|
||||
```typescript {% title="Good context" %}
|
||||
monitoring.captureException(error, {
|
||||
userId: user.id,
|
||||
action: 'export_csv',
|
||||
rowCount: data.length,
|
||||
filters: appliedFilters,
|
||||
exportFormat: 'csv',
|
||||
});
|
||||
```
|
||||
|
||||
```typescript {% title="Less useful context" %}
|
||||
monitoring.captureException(error, {
|
||||
error: 'something went wrong',
|
||||
time: Date.now(),
|
||||
});
|
||||
```
|
||||
|
||||
{% faq
|
||||
title="Frequently Asked Questions"
|
||||
items=[
|
||||
{"question": "Should I capture validation errors?", "answer": "Generally no. Validation errors are expected and user-facing. Capture unexpected errors like database failures, third-party API errors, or logic errors that shouldn't happen."},
|
||||
{"question": "How do I avoid capturing the same error multiple times?", "answer": "Capture at the highest level where you handle the error. If you rethrow an error, don't capture it at the lower level. Let it bubble up to where it's finally handled."},
|
||||
{"question": "What's the difference between captureException and captureEvent?", "answer": "captureException is for errors and exceptions. captureEvent is for tracking important actions or milestones that aren't errors, like 'user_deleted_account' or 'large_export_started'."},
|
||||
{"question": "Does capturing errors affect performance?", "answer": "Minimally. Error capturing is asynchronous and non-blocking. However, avoid capturing in hot paths or loops. Capture at boundaries, not in every function."}
|
||||
]
|
||||
/%}
|
||||
|
||||
**Next:** [Creating a Custom Monitoring Provider →](custom-provider)
|
||||
334
docs/monitoring/custom-monitoring-provider.mdoc
Normal file
334
docs/monitoring/custom-monitoring-provider.mdoc
Normal file
@@ -0,0 +1,334 @@
|
||||
---
|
||||
status: "published"
|
||||
title: 'Creating a Custom Monitoring Provider'
|
||||
label: 'Custom Provider'
|
||||
description: 'Integrate LogRocket, Bugsnag, Datadog, or any monitoring service by implementing the MonitoringService interface.'
|
||||
order: 1
|
||||
---
|
||||
|
||||
{% sequence title="How to create a custom monitoring provider" description="Add your preferred monitoring service to the kit." %}
|
||||
|
||||
[Implement the MonitoringService interface](#implement-the-monitoringservice-interface)
|
||||
|
||||
[Register the provider](#register-the-provider)
|
||||
|
||||
[Configure for server-side](#server-side-configuration)
|
||||
|
||||
{% /sequence %}
|
||||
|
||||
The monitoring system uses a registry pattern that loads providers dynamically based on the `NEXT_PUBLIC_MONITORING_PROVIDER` environment variable. You can add support for LogRocket, Bugsnag, Datadog, or any other service.
|
||||
|
||||
## Implement the MonitoringService Interface
|
||||
|
||||
Create a new package or add to the existing monitoring packages:
|
||||
|
||||
```typescript {% title="packages/monitoring/logrocket/src/logrocket-monitoring.service.ts" %}
|
||||
import LogRocket from 'logrocket';
|
||||
import { MonitoringService } from '@kit/monitoring-core';
|
||||
|
||||
export class LogRocketMonitoringService implements MonitoringService {
|
||||
private readonly readyPromise: Promise<unknown>;
|
||||
private readyResolver?: (value?: unknown) => void;
|
||||
|
||||
constructor() {
|
||||
this.readyPromise = new Promise(
|
||||
(resolve) => (this.readyResolver = resolve),
|
||||
);
|
||||
|
||||
void this.initialize();
|
||||
}
|
||||
|
||||
async ready() {
|
||||
return this.readyPromise;
|
||||
}
|
||||
|
||||
captureException(error: Error, extra?: Record<string, unknown>) {
|
||||
LogRocket.captureException(error, {
|
||||
extra,
|
||||
});
|
||||
}
|
||||
|
||||
captureEvent(event: string, extra?: Record<string, unknown>) {
|
||||
LogRocket.track(event, extra);
|
||||
}
|
||||
|
||||
identifyUser(user: { id: string; email?: string; name?: string }) {
|
||||
LogRocket.identify(user.id, {
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
});
|
||||
}
|
||||
|
||||
private async initialize() {
|
||||
const appId = process.env.NEXT_PUBLIC_LOGROCKET_APP_ID;
|
||||
|
||||
if (!appId) {
|
||||
console.warn('LogRocket app ID not configured');
|
||||
this.readyResolver?.();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
LogRocket.init(appId);
|
||||
}
|
||||
|
||||
this.readyResolver?.();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Package Configuration
|
||||
|
||||
Create the package structure:
|
||||
|
||||
```json {% title="packages/monitoring/logrocket/package.json" %}
|
||||
{
|
||||
"name": "@kit/logrocket",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kit/monitoring-core": "workspace:*",
|
||||
"logrocket": "^3.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript {% title="packages/monitoring/logrocket/src/index.ts" %}
|
||||
export { LogRocketMonitoringService } from './logrocket-monitoring.service';
|
||||
```
|
||||
|
||||
## Register the Provider
|
||||
|
||||
### Client-Side Registration
|
||||
|
||||
Update the monitoring provider registry:
|
||||
|
||||
```typescript {% title="packages/monitoring/api/src/components/provider.tsx" %}
|
||||
import { lazy } from 'react';
|
||||
import { createRegistry } from '@kit/shared/registry';
|
||||
import {
|
||||
MonitoringProvider as MonitoringProviderType,
|
||||
getMonitoringProvider,
|
||||
} from '../get-monitoring-provider';
|
||||
|
||||
type ProviderComponent = {
|
||||
default: React.ComponentType<React.PropsWithChildren>;
|
||||
};
|
||||
|
||||
const provider = getMonitoringProvider();
|
||||
|
||||
const Provider = provider
|
||||
? lazy(() => monitoringProviderRegistry.get(provider))
|
||||
: null;
|
||||
|
||||
const monitoringProviderRegistry = createRegistry<
|
||||
ProviderComponent,
|
||||
NonNullable<MonitoringProviderType>
|
||||
>();
|
||||
|
||||
// Existing Sentry registration
|
||||
monitoringProviderRegistry.register('sentry', async () => {
|
||||
const { SentryProvider } = await import('@kit/sentry/provider');
|
||||
return {
|
||||
default: function SentryProviderWrapper({ children }) {
|
||||
return <SentryProvider>{children}</SentryProvider>;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Add LogRocket registration
|
||||
monitoringProviderRegistry.register('logrocket', async () => {
|
||||
const { LogRocketProvider } = await import('@kit/logrocket/provider');
|
||||
return {
|
||||
default: function LogRocketProviderWrapper({ children }) {
|
||||
return <LogRocketProvider>{children}</LogRocketProvider>;
|
||||
},
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
### Add Provider Type
|
||||
|
||||
Update the provider enum:
|
||||
|
||||
```typescript {% title="packages/monitoring/api/src/get-monitoring-provider.ts" %}
|
||||
import * as z from 'zod';
|
||||
|
||||
const MONITORING_PROVIDERS = [
|
||||
'sentry',
|
||||
'logrocket', // Add your provider
|
||||
'',
|
||||
] as const;
|
||||
|
||||
export const MONITORING_PROVIDER = z
|
||||
.enum(MONITORING_PROVIDERS)
|
||||
.optional()
|
||||
.transform((value) => value || undefined);
|
||||
|
||||
export type MonitoringProvider = z.output<typeof MONITORING_PROVIDER>;
|
||||
|
||||
export function getMonitoringProvider() {
|
||||
const result = MONITORING_PROVIDER.safeParse(process.env.NEXT_PUBLIC_MONITORING_PROVIDER);
|
||||
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
```
|
||||
|
||||
### Create the Provider Component
|
||||
|
||||
```typescript {% title="packages/monitoring/logrocket/src/provider.tsx" %}
|
||||
import { MonitoringContext } from '@kit/monitoring-core';
|
||||
import { LogRocketMonitoringService } from './logrocket-monitoring.service';
|
||||
|
||||
const logrocket = new LogRocketMonitoringService();
|
||||
|
||||
export function LogRocketProvider({ children }: React.PropsWithChildren) {
|
||||
return (
|
||||
<MonitoringContext.Provider value={logrocket}>
|
||||
{children}
|
||||
</MonitoringContext.Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Server-Side Configuration
|
||||
|
||||
Register the provider for server-side error capture:
|
||||
|
||||
```typescript {% title="packages/monitoring/api/src/services/get-server-monitoring-service.ts" %}
|
||||
import {
|
||||
ConsoleMonitoringService,
|
||||
MonitoringService,
|
||||
} from '@kit/monitoring-core';
|
||||
import { createRegistry } from '@kit/shared/registry';
|
||||
import {
|
||||
MonitoringProvider,
|
||||
getMonitoringProvider,
|
||||
} from '../get-monitoring-provider';
|
||||
|
||||
const serverMonitoringRegistry = createRegistry<
|
||||
MonitoringService,
|
||||
NonNullable<MonitoringProvider>
|
||||
>();
|
||||
|
||||
// Existing Sentry registration
|
||||
serverMonitoringRegistry.register('sentry', async () => {
|
||||
const { SentryMonitoringService } = await import('@kit/sentry');
|
||||
return new SentryMonitoringService();
|
||||
});
|
||||
|
||||
// Add LogRocket registration
|
||||
serverMonitoringRegistry.register('logrocket', async () => {
|
||||
const { LogRocketMonitoringService } = await import('@kit/logrocket');
|
||||
return new LogRocketMonitoringService();
|
||||
});
|
||||
|
||||
export async function getServerMonitoringService() {
|
||||
const provider = getMonitoringProvider();
|
||||
|
||||
if (!provider) {
|
||||
return new ConsoleMonitoringService();
|
||||
}
|
||||
|
||||
return serverMonitoringRegistry.get(provider);
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Add your provider's configuration:
|
||||
|
||||
```bash {% title="apps/web/.env.local" %}
|
||||
# Enable LogRocket as the monitoring provider
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=logrocket
|
||||
|
||||
# LogRocket configuration
|
||||
NEXT_PUBLIC_LOGROCKET_APP_ID=your-org/your-app
|
||||
```
|
||||
|
||||
## Example: Datadog Integration
|
||||
|
||||
Here's a complete example for Datadog RUM:
|
||||
|
||||
```typescript {% title="packages/monitoring/datadog/src/datadog-monitoring.service.ts" %}
|
||||
import { datadogRum } from '@datadog/browser-rum';
|
||||
import { MonitoringService } from '@kit/monitoring-core';
|
||||
|
||||
export class DatadogMonitoringService implements MonitoringService {
|
||||
private readonly readyPromise: Promise<unknown>;
|
||||
private readyResolver?: (value?: unknown) => void;
|
||||
|
||||
constructor() {
|
||||
this.readyPromise = new Promise(
|
||||
(resolve) => (this.readyResolver = resolve),
|
||||
);
|
||||
|
||||
void this.initialize();
|
||||
}
|
||||
|
||||
async ready() {
|
||||
return this.readyPromise;
|
||||
}
|
||||
|
||||
captureException(error: Error, extra?: Record<string, unknown>) {
|
||||
datadogRum.addError(error, {
|
||||
...extra,
|
||||
});
|
||||
}
|
||||
|
||||
captureEvent(event: string, extra?: Record<string, unknown>) {
|
||||
datadogRum.addAction(event, extra);
|
||||
}
|
||||
|
||||
identifyUser(user: { id: string; email?: string; name?: string }) {
|
||||
datadogRum.setUser({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
});
|
||||
}
|
||||
|
||||
private async initialize() {
|
||||
if (typeof window === 'undefined') {
|
||||
this.readyResolver?.();
|
||||
return;
|
||||
}
|
||||
|
||||
datadogRum.init({
|
||||
applicationId: process.env.NEXT_PUBLIC_DATADOG_APP_ID!,
|
||||
clientToken: process.env.NEXT_PUBLIC_DATADOG_CLIENT_TOKEN!,
|
||||
site: process.env.NEXT_PUBLIC_DATADOG_SITE ?? 'datadoghq.com',
|
||||
service: process.env.NEXT_PUBLIC_DATADOG_SERVICE ?? 'my-saas',
|
||||
env: process.env.NEXT_PUBLIC_DATADOG_ENV ?? 'production',
|
||||
sessionSampleRate: 100,
|
||||
sessionReplaySampleRate: 20,
|
||||
trackUserInteractions: true,
|
||||
trackResources: true,
|
||||
trackLongTasks: true,
|
||||
});
|
||||
|
||||
this.readyResolver?.();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Gotchas
|
||||
|
||||
1. **Browser-only initialization** - Check `typeof window !== 'undefined'` before accessing browser APIs.
|
||||
2. **Ready state** - The `ready()` method must resolve after initialization completes. Server contexts call `await service.ready()` before capturing.
|
||||
3. **Provider enum** - Remember to add your provider to the `MONITORING_PROVIDERS` array in `get-monitoring-provider.ts`.
|
||||
4. **Lazy loading** - Providers are loaded lazily through the registry. Don't import the monitoring service directly in your main bundle.
|
||||
5. **Server vs client** - Some providers (like LogRocket) are browser-only. Return a no-op or console fallback for server contexts.
|
||||
|
||||
This monitoring system is part of the [Next.js Supabase SaaS Kit](/next-supabase-turbo).
|
||||
|
||||
---
|
||||
|
||||
**Previous:** [Sentry Configuration ←](./sentry)
|
||||
39
docs/monitoring/honeybadger.mdoc
Normal file
39
docs/monitoring/honeybadger.mdoc
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
status: "published"
|
||||
title: "Configuring Honeybadger Monitoring in Your Next.js Supabase SaaS Kit"
|
||||
label: "Honeybadger"
|
||||
order: 5
|
||||
description: "Set up Honeybadger as your error monitoring provider in Makerkit, combining analytics and error tracking in one platform."
|
||||
---
|
||||
|
||||
[Honeybadger](https://honeybadger.io/) is a platform for error monitoring and uptime tracking with zero-config alerts.
|
||||
|
||||
## Installing the Honeybadger Plugin
|
||||
|
||||
Honeybadger is distributed as a Makerkit plugin. Install it using the CLI:
|
||||
|
||||
```bash
|
||||
npx @makerkit/cli@latest plugins add honeybadger
|
||||
```
|
||||
|
||||
The Makerkit CLI will automatically wire up the plugin in your project, so you don't have to do anything manually.
|
||||
|
||||
Please review the changes with `git diff`.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Set the monitoring provider and Honeybadger configuration:
|
||||
|
||||
```bash title="apps/web/.env.local"
|
||||
# Enable Honeybadger as the monitoring provider
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=honeybadger
|
||||
|
||||
# Honeybadger configuration
|
||||
NEXT_PUBLIC_HONEYBADGER_API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
Please add these environment variables to your hosting provider when you deploy your application to production.
|
||||
|
||||
## Scope of this plugin
|
||||
|
||||
This plugin is only responsible for capturing errors and exceptions in the browser and server. Uploading sourcemaps is not supported yet. Please use the [Honeybadger guide](https://docs.honeybadger.io/lib/javascript/integration/nextjs/) for more information on all the available options.
|
||||
347
docs/monitoring/overview.mdoc
Normal file
347
docs/monitoring/overview.mdoc
Normal file
@@ -0,0 +1,347 @@
|
||||
---
|
||||
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)
|
||||
146
docs/monitoring/posthog.mdoc
Normal file
146
docs/monitoring/posthog.mdoc
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
status: "published"
|
||||
title: "Configuring PostHog Monitoring in Your Next.js Supabase SaaS Kit"
|
||||
label: "PostHog"
|
||||
order: 3
|
||||
description: "Set up PostHog as your error monitoring provider in Makerkit, combining analytics and error tracking in one platform."
|
||||
---
|
||||
|
||||
{% sequence title="Steps to configure PostHog monitoring" description="Learn how to configure PostHog for error monitoring in your Next.js Supabase SaaS kit." %}
|
||||
|
||||
[Installing the PostHog plugin](#installing-the-posthog-plugin)
|
||||
|
||||
[Registering the monitoring service](#registering-the-monitoring-service)
|
||||
|
||||
[Environment variables](#environment-variables)
|
||||
|
||||
[How PostHog monitoring works](#how-posthog-monitoring-works)
|
||||
|
||||
{% /sequence %}
|
||||
|
||||
[PostHog](https://posthog.com) combines product analytics, session replay, feature flags, and error tracking in one platform. If you're already using PostHog for analytics, adding it as your monitoring provider lets you correlate errors with user behavior without switching between tools.
|
||||
|
||||
{% alert type="default" title="Already using PostHog for analytics?" %}
|
||||
If you've set up PostHog using the [PostHog Analytics guide](../analytics/posthog-analytics-provider), you already have the plugin installed. Skip to [Registering the monitoring service](#registering-the-monitoring-service).
|
||||
{% /alert %}
|
||||
|
||||
## Installing the PostHog Plugin
|
||||
|
||||
PostHog is distributed as a Makerkit plugin. Install it using the CLI:
|
||||
|
||||
```bash
|
||||
npx @makerkit/cli@latest plugins add posthog
|
||||
```
|
||||
|
||||
The Makerkit CLI will automatically wire up the plugin in your project, so you don't have to do anything manually. Please review the changes with `git diff`.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Set the monitoring provider and PostHog configuration:
|
||||
|
||||
```bash title=".env.local"
|
||||
# Enable PostHog as the monitoring provider
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=posthog
|
||||
|
||||
# PostHog configuration (same as analytics setup)
|
||||
NEXT_PUBLIC_POSTHOG_KEY=phc_your_key_here
|
||||
NEXT_PUBLIC_POSTHOG_HOST=https://eu.posthog.com
|
||||
```
|
||||
|
||||
If you haven't configured PostHog yet, see the [PostHog Analytics guide](/docs/next-supabase-turbo/analytics/posthog-analytics-provider) for details on:
|
||||
|
||||
- Finding your API key
|
||||
- Choosing your region (EU vs US)
|
||||
- Setting up ingestion rewrites to bypass ad blockers
|
||||
|
||||
## How PostHog Monitoring Works
|
||||
|
||||
When PostHog is your monitoring provider, errors are captured and sent to PostHog's error tracking system:
|
||||
|
||||
### Exception capture
|
||||
|
||||
PostHog captures:
|
||||
|
||||
- Client-side React errors
|
||||
- Unhandled promise rejections
|
||||
- Server-side exceptions via the Next.js instrumentation hook
|
||||
|
||||
Errors appear in PostHog under **Error Tracking** in the sidebar.
|
||||
|
||||
### Session correlation
|
||||
|
||||
The main benefit of using PostHog for monitoring is that errors are automatically linked to session replays. When you view an error in PostHog, you can:
|
||||
|
||||
1. See the exact session where the error occurred
|
||||
2. Watch the user's actions leading up to the error
|
||||
3. Correlate errors with feature flag states
|
||||
4. See the user's full journey through your app
|
||||
|
||||
This is particularly useful for debugging errors that only happen in specific user flows or under certain conditions.
|
||||
|
||||
### User identification
|
||||
|
||||
When a user signs in, Makerkit identifies them in PostHog. This links errors to specific users, so you can:
|
||||
|
||||
- See all errors for a specific user
|
||||
- Contact users affected by critical bugs
|
||||
- Filter errors by user properties (plan, account type, etc.)
|
||||
|
||||
## Limitations
|
||||
|
||||
PostHog's error tracking is newer than dedicated tools like Sentry. Consider these limitations:
|
||||
|
||||
| Feature | PostHog | Sentry |
|
||||
|---------|---------|--------|
|
||||
| Error tracking | Yes | Yes |
|
||||
| Stack trace deobfuscation | Limited | Full source map support |
|
||||
| Performance monitoring | Via analytics | Full APM |
|
||||
| Release tracking | No | Yes |
|
||||
| Issue assignment | No | Yes |
|
||||
| Slack/PagerDuty integration | Limited | Full |
|
||||
|
||||
**When to choose PostHog for monitoring:**
|
||||
|
||||
- You're already using PostHog for analytics
|
||||
- You want errors correlated with session replays
|
||||
- Your error volume is moderate
|
||||
- You prefer fewer tools to manage
|
||||
|
||||
**When to choose Sentry instead:**
|
||||
|
||||
- You need detailed stack traces with source maps
|
||||
- You have high error volume
|
||||
- You need advanced alerting and issue management
|
||||
- You want dedicated performance monitoring
|
||||
|
||||
## Using Both PostHog and Sentry
|
||||
|
||||
You can use PostHog for analytics and Sentry for monitoring. Set `NEXT_PUBLIC_MONITORING_PROVIDER=sentry` while keeping PostHog configured for analytics. This gives you:
|
||||
|
||||
- PostHog: Analytics, session replay, feature flags
|
||||
- Sentry: Error tracking, performance monitoring, source maps
|
||||
|
||||
## Verification
|
||||
|
||||
After setup:
|
||||
|
||||
1. Trigger a test error in your application
|
||||
2. Open PostHog and navigate to **Error Tracking**
|
||||
3. Verify the error appears with:
|
||||
- Error message and stack trace
|
||||
- User information (if logged in)
|
||||
- Link to session replay
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Errors not appearing in PostHog
|
||||
|
||||
1. **Check the provider setting**: Verify `NEXT_PUBLIC_MONITORING_PROVIDER=posthog`
|
||||
2. **Check PostHog initialization**: Open browser DevTools and look for PostHog network requests
|
||||
3. **Verify the registrations**: Ensure all three files are updated with PostHog registrations
|
||||
4. **Check ad blockers**: If using PostHog directly (no ingestion rewrites), ad blockers may block requests
|
||||
|
||||
## Next Steps
|
||||
- [Learn about PostHog's error tracking features](https://posthog.com/docs/error-tracking)
|
||||
- [Configure session replay settings](https://posthog.com/docs/session-replay)
|
||||
- [Return to monitoring overview](/docs/next-supabase-turbo/monitoring/overview)
|
||||
302
docs/monitoring/sentry.mdoc
Normal file
302
docs/monitoring/sentry.mdoc
Normal file
@@ -0,0 +1,302 @@
|
||||
---
|
||||
status: "published"
|
||||
title: "Configuring Sentry in Your Next.js Supabase SaaS Kit"
|
||||
label: "Sentry"
|
||||
order: 3
|
||||
description: "Set up Sentry for error tracking, performance monitoring, and session replay in your Makerkit application."
|
||||
---
|
||||
|
||||
{% sequence title="Steps to configure Sentry" description="Learn how to configure Sentry in your Next.js Supabase SaaS kit." %}
|
||||
|
||||
[Installing the Sentry SDK](#installing-the-sentry-sdk)
|
||||
|
||||
[Environment variables](#environment-variables)
|
||||
|
||||
[Configuring source maps](#configuring-source-maps)
|
||||
|
||||
[Customizing the Sentry configuration](#customizing-the-sentry-configuration)
|
||||
|
||||
[Sentry features in Makerkit](#sentry-features-in-makerkit)
|
||||
|
||||
{% /sequence %}
|
||||
|
||||
[Sentry](https://sentry.io) is the recommended monitoring provider for Makerkit applications. It provides error tracking, performance monitoring, and session replay out of the box. Sentry is included in Makerkit's core packages, so no plugin installation is required.
|
||||
|
||||
## Installing the Sentry SDK
|
||||
|
||||
Install the `@sentry/nextjs` package in your web application:
|
||||
|
||||
```bash
|
||||
pnpm add @sentry/nextjs --filter web
|
||||
```
|
||||
|
||||
This package provides the Next.js-specific integrations for Sentry, including automatic instrumentation for Server Components, Server Actions, and Route Handlers.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Add these variables to your `.env.local` file:
|
||||
|
||||
```bash title=".env.local"
|
||||
# Required: Enable Sentry as your monitoring provider
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=sentry
|
||||
|
||||
# Required: Your Sentry DSN (found in Sentry project settings)
|
||||
NEXT_PUBLIC_SENTRY_DSN=https://abc123@o123456.ingest.sentry.io/123456
|
||||
|
||||
# Optional: Set the environment (defaults to VERCEL_ENV if not set)
|
||||
NEXT_PUBLIC_SENTRY_ENVIRONMENT=production
|
||||
```
|
||||
|
||||
You can find your DSN in the Sentry dashboard under **Project Settings > Client Keys (DSN)**.
|
||||
|
||||
## Configuring Source Maps
|
||||
|
||||
Source maps let Sentry show you the original source code in error stack traces instead of minified production code. This is essential for debugging production errors.
|
||||
|
||||
### 1. Update your Next.js configuration
|
||||
|
||||
Wrap your Next.js configuration with Sentry's build plugin:
|
||||
|
||||
```typescript title="next.config.mjs"
|
||||
import { withSentryConfig } from '@sentry/nextjs';
|
||||
|
||||
const nextConfig = {
|
||||
// Your existing Next.js config
|
||||
};
|
||||
|
||||
export default withSentryConfig(nextConfig, {
|
||||
// Sentry organization slug
|
||||
org: 'your-sentry-org',
|
||||
|
||||
// Sentry project name
|
||||
project: 'your-sentry-project',
|
||||
|
||||
// Auth token for uploading source maps (set in CI)
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
|
||||
// Suppress logs in non-production builds
|
||||
silent: process.env.NODE_ENV !== 'production',
|
||||
|
||||
// Upload source maps from all packages in the monorepo
|
||||
widenClientFileUpload: true,
|
||||
|
||||
// Disable automatic server function instrumentation
|
||||
// (Makerkit handles this via the monitoring package)
|
||||
autoInstrumentServerFunctions: false,
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Create a Sentry auth token
|
||||
|
||||
Generate an auth token in your Sentry account:
|
||||
|
||||
1. Go to **Settings > Auth Tokens** in Sentry
|
||||
2. Click **Create New Token**
|
||||
3. Select the `project:releases` and `org:read` scopes
|
||||
4. Copy the token
|
||||
|
||||
### 3. Add the token to your CI environment
|
||||
|
||||
Add the `SENTRY_AUTH_TOKEN` to your deployment platform's environment variables:
|
||||
|
||||
```bash
|
||||
# Vercel, Railway, Render, etc.
|
||||
SENTRY_AUTH_TOKEN=sntrys_eyJ...
|
||||
```
|
||||
|
||||
{% alert type="warning" title="Don't commit this token" %}
|
||||
The `SENTRY_AUTH_TOKEN` should only exist in your CI/CD environment, not in your `.env.local` or committed to git. It has write access to your Sentry project.
|
||||
{% /alert %}
|
||||
|
||||
## Customizing the Sentry Configuration
|
||||
|
||||
Makerkit initializes Sentry with sensible defaults. You can customize these by modifying the configuration in the Sentry package.
|
||||
|
||||
### Client-side configuration
|
||||
|
||||
Edit `packages/monitoring/sentry/src/sentry.client.config.ts`:
|
||||
|
||||
```typescript title="packages/monitoring/sentry/src/sentry.client.config.ts"
|
||||
import { init } from '@sentry/nextjs';
|
||||
|
||||
export function initializeSentryBrowserClient(
|
||||
props: Parameters<typeof init>[0] = {},
|
||||
) {
|
||||
return init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
|
||||
// Sample 100% of transactions for performance monitoring
|
||||
// Reduce this in high-traffic applications
|
||||
tracesSampleRate: props?.tracesSampleRate ?? 1.0,
|
||||
|
||||
// Capture 10% of sessions for replay
|
||||
replaysSessionSampleRate: 0.1,
|
||||
|
||||
// Capture 100% of sessions with errors for replay
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
// Add custom integrations
|
||||
integrations: [
|
||||
// Example: Add breadcrumbs for console logs
|
||||
// Sentry.breadcrumbsIntegration({ console: true }),
|
||||
],
|
||||
|
||||
...props,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Server-side configuration
|
||||
|
||||
Edit `packages/monitoring/sentry/src/sentry.server.config.ts`:
|
||||
|
||||
```typescript title="packages/monitoring/sentry/src/sentry.server.config.ts"
|
||||
import { init } from '@sentry/nextjs';
|
||||
|
||||
export function initializeSentryServerClient(
|
||||
props: Parameters<typeof init>[0] = {},
|
||||
) {
|
||||
return init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
|
||||
// Sample rate for server-side transactions
|
||||
tracesSampleRate: props?.tracesSampleRate ?? 1.0,
|
||||
|
||||
...props,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Adjusting sample rates for production
|
||||
|
||||
For high-traffic applications, sampling 100% of transactions can be expensive. Adjust the sample rates based on your traffic:
|
||||
|
||||
| Monthly requests | Recommended `tracesSampleRate` |
|
||||
|-----------------|-------------------------------|
|
||||
| < 100k | 1.0 (100%) |
|
||||
| 100k - 1M | 0.1 - 0.5 (10-50%) |
|
||||
| > 1M | 0.01 - 0.1 (1-10%) |
|
||||
|
||||
Error capture is not affected by sampling. All errors are captured regardless of the `tracesSampleRate` setting.
|
||||
|
||||
## Sentry Features in Makerkit
|
||||
|
||||
### Error tracking
|
||||
|
||||
All uncaught exceptions are automatically captured:
|
||||
|
||||
- **Client-side**: React errors, unhandled promise rejections
|
||||
- **Server-side**: Server Component errors, Server Action errors, Route Handler errors, Middleware errors
|
||||
|
||||
Each error includes:
|
||||
|
||||
- Full stack trace (with source maps)
|
||||
- Request context (URL, method, headers)
|
||||
- User information (if identified)
|
||||
- Environment and release information
|
||||
|
||||
### Performance monitoring
|
||||
|
||||
When `tracesSampleRate` is greater than 0, Sentry tracks:
|
||||
|
||||
- Page load times
|
||||
- API route response times
|
||||
- Server Component render times
|
||||
- Database query durations (if using Sentry's database integrations)
|
||||
|
||||
### Session replay
|
||||
|
||||
Sentry can record user sessions and replay them when errors occur. This helps you see exactly what the user did before encountering an error.
|
||||
|
||||
Session replay is enabled by default with these settings:
|
||||
|
||||
- 10% of normal sessions are recorded
|
||||
- 100% of sessions with errors are recorded
|
||||
|
||||
To disable replay, set both sample rates to 0 in your client config.
|
||||
|
||||
### User identification
|
||||
|
||||
Makerkit automatically identifies users when they sign in through the events system. You can also manually identify users:
|
||||
|
||||
```typescript
|
||||
import { useMonitoring } from '@kit/monitoring/hooks';
|
||||
|
||||
function UserProfile({ user }) {
|
||||
const monitoring = useMonitoring();
|
||||
|
||||
useEffect(() => {
|
||||
monitoring.identifyUser({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
username: user.name,
|
||||
});
|
||||
}, [user]);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Your Setup
|
||||
|
||||
After configuration, verify Sentry is working:
|
||||
|
||||
### 1. Trigger a test error
|
||||
|
||||
Add a temporary button to trigger an error:
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
export function TestSentry() {
|
||||
return (
|
||||
<button
|
||||
onClick={() => {
|
||||
throw new Error('Test Sentry error');
|
||||
}}
|
||||
>
|
||||
Test Sentry
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Check the Sentry dashboard
|
||||
|
||||
The error should appear in your Sentry project within a few seconds. Verify:
|
||||
|
||||
- The stack trace shows your original source code (not minified)
|
||||
- The environment is correct
|
||||
- User information is attached (if logged in)
|
||||
|
||||
### 3. Remove the test code
|
||||
|
||||
Delete the test button after verifying the setup.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Errors not appearing in Sentry
|
||||
|
||||
1. **Check the DSN**: Verify `NEXT_PUBLIC_SENTRY_DSN` is set correctly
|
||||
2. **Check the provider**: Verify `NEXT_PUBLIC_MONITORING_PROVIDER=sentry`
|
||||
3. **Check the console**: Look for Sentry initialization errors
|
||||
4. **Check ad blockers**: Some ad blockers block Sentry's ingestion endpoint
|
||||
|
||||
### Source maps not working
|
||||
|
||||
1. **Verify the auth token**: Check `SENTRY_AUTH_TOKEN` is set in your CI environment
|
||||
2. **Check the build logs**: Look for "Uploading source maps" in your build output
|
||||
3. **Verify the release**: Make sure the release version matches between your build and Sentry
|
||||
|
||||
### High Sentry costs
|
||||
|
||||
1. **Reduce `tracesSampleRate`**: Lower the performance monitoring sample rate
|
||||
2. **Reduce `replaysSessionSampleRate`**: Only capture error sessions
|
||||
3. **Filter events**: Use Sentry's `beforeSend` hook to drop low-value errors
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [View the Sentry Next.js documentation](https://docs.sentry.io/platforms/javascript/guides/nextjs/)
|
||||
- [Set up Sentry alerts](https://docs.sentry.io/product/alerts/)
|
||||
- [Configure Slack notifications](https://docs.sentry.io/product/integrations/notification-incidents/slack/)
|
||||
- [Return to monitoring overview](/docs/next-supabase-turbo/monitoring/overview)
|
||||
270
docs/monitoring/signoz.mdoc
Normal file
270
docs/monitoring/signoz.mdoc
Normal file
@@ -0,0 +1,270 @@
|
||||
---
|
||||
status: "published"
|
||||
title: "Configuring SigNoz in Your Next.js Supabase SaaS Kit"
|
||||
label: "SigNoz"
|
||||
order: 6
|
||||
description: "Set up SigNoz for OpenTelemetry-native observability with self-hosted error tracking, traces, logs, and metrics."
|
||||
---
|
||||
|
||||
{% sequence title="Steps to configure SigNoz" description="Learn how to configure SigNoz in your Next.js Supabase SaaS kit." %}
|
||||
|
||||
[Installing the SigNoz plugin](#installing-the-signoz-plugin)
|
||||
|
||||
[Registering the monitoring services](#registering-the-monitoring-services)
|
||||
|
||||
[Environment variables](#environment-variables)
|
||||
|
||||
[Running SigNoz locally](#running-signoz-locally)
|
||||
|
||||
[Configuring logging with Winston](#configuring-logging-with-winston)
|
||||
|
||||
{% /sequence %}
|
||||
|
||||
[SigNoz](https://signoz.io) is an open-source, self-hostable observability platform built on OpenTelemetry. It provides traces, metrics, logs, and error tracking in one interface. Choose SigNoz if you want full control over your observability data and prefer OpenTelemetry standards.
|
||||
|
||||
## Installing the SigNoz Plugin
|
||||
|
||||
SigNoz is distributed as a Makerkit plugin. Install it using the CLI:
|
||||
|
||||
```bash
|
||||
npx @makerkit/cli@latest plugins add signoz
|
||||
```
|
||||
|
||||
This creates the plugin at `packages/plugins/signoz`.
|
||||
|
||||
Our codemod will wire up the plugin in your project, so you don't have to do anything manually. Please review the changes with `git diff`.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
SigNoz requires several OpenTelemetry environment variables:
|
||||
|
||||
```bash title=".env.local"
|
||||
# Enable SigNoz as the monitoring provider
|
||||
NEXT_PUBLIC_MONITORING_PROVIDER=signoz
|
||||
|
||||
# Service identification
|
||||
NEXT_PUBLIC_OTEL_SERVICE_NAME=makerkit
|
||||
OTEL_RESOURCE_ATTRIBUTES="service.name=makerkit,service.version=1.0.0"
|
||||
|
||||
# Client-side SigNoz configuration
|
||||
NEXT_PUBLIC_SIGNOZ_INGESTION_KEY=your_ingestion_key
|
||||
NEXT_PUBLIC_SIGNOZ_INGESTION_URL=http://localhost:4318/v1/logs
|
||||
|
||||
# Server-side OpenTelemetry configuration
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
||||
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
|
||||
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
|
||||
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://localhost:4318/v1/logs
|
||||
OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=your_ingestion_key"
|
||||
```
|
||||
|
||||
### Variable reference
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `NEXT_PUBLIC_OTEL_SERVICE_NAME` | Name shown in SigNoz dashboards |
|
||||
| `OTEL_RESOURCE_ATTRIBUTES` | Service metadata (name, version) |
|
||||
| `NEXT_PUBLIC_SIGNOZ_INGESTION_KEY` | Your SigNoz ingestion API key |
|
||||
| `NEXT_PUBLIC_SIGNOZ_INGESTION_URL` | Logs ingestion endpoint |
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Base OTLP endpoint |
|
||||
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Traces ingestion endpoint |
|
||||
| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Logs ingestion endpoint |
|
||||
|
||||
## Running SigNoz Locally
|
||||
|
||||
For development, run SigNoz in Docker:
|
||||
|
||||
```bash
|
||||
# Clone SigNoz
|
||||
git clone -b main https://github.com/SigNoz/signoz.git && cd signoz/deploy/
|
||||
|
||||
# Start SigNoz
|
||||
docker compose -f docker/clickhouse-setup/docker-compose.yaml up -d
|
||||
```
|
||||
|
||||
SigNoz will be available at `http://localhost:3301`.
|
||||
|
||||
For detailed installation options, see the [SigNoz Docker installation guide](https://signoz.io/docs/install/docker/).
|
||||
|
||||
### Local environment variables
|
||||
|
||||
When running SigNoz locally, use these endpoints:
|
||||
|
||||
```bash title=".env.local"
|
||||
NEXT_PUBLIC_SIGNOZ_INGESTION_URL=http://localhost:4318/v1/logs
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
||||
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
|
||||
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://localhost:4318/v1/logs
|
||||
```
|
||||
|
||||
For local development, you can leave `OTEL_EXPORTER_OTLP_HEADERS` empty since no authentication is required.
|
||||
|
||||
## Production Configuration
|
||||
|
||||
When deploying SigNoz to production (self-hosted or SigNoz Cloud):
|
||||
|
||||
### Self-hosted
|
||||
|
||||
Update the endpoints to point to your SigNoz instance:
|
||||
|
||||
```bash title=".env.production"
|
||||
NEXT_PUBLIC_SIGNOZ_INGESTION_URL=https://signoz.yourdomain.com/v1/logs
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=https://signoz.yourdomain.com
|
||||
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://signoz.yourdomain.com/v1/traces
|
||||
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://signoz.yourdomain.com/v1/logs
|
||||
```
|
||||
|
||||
### SigNoz Cloud
|
||||
|
||||
If using SigNoz Cloud, update the endpoints and add your ingestion key:
|
||||
|
||||
```bash title=".env.production"
|
||||
NEXT_PUBLIC_SIGNOZ_INGESTION_KEY=your_cloud_ingestion_key
|
||||
NEXT_PUBLIC_SIGNOZ_INGESTION_URL=https://ingest.{region}.signoz.cloud/v1/logs
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.{region}.signoz.cloud
|
||||
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://ingest.{region}.signoz.cloud/v1/traces
|
||||
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://ingest.{region}.signoz.cloud/v1/logs
|
||||
OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=your_cloud_ingestion_key"
|
||||
```
|
||||
|
||||
Replace `{region}` with your SigNoz Cloud region (e.g., `us`, `eu`).
|
||||
|
||||
## Configuring Logging with Winston
|
||||
|
||||
{% alert type="warning" title="Pino logging limitation" %}
|
||||
Due to compatibility issues between Next.js and the OpenTelemetry transport for Pino (Makerkit's default logger), logs cannot be sent to SigNoz using Pino. Switch to Winston for log ingestion.
|
||||
{% /alert %}
|
||||
|
||||
### 1. Switch to Winston
|
||||
|
||||
Set the logger environment variable:
|
||||
|
||||
```bash title=".env.local"
|
||||
LOGGER=winston
|
||||
```
|
||||
|
||||
### 2. Register the Winston logger
|
||||
|
||||
```typescript title="packages/shared/src/logger/index.ts"
|
||||
// Register the Winston logger implementation
|
||||
loggerRegistry.register('winston', async () => {
|
||||
const { Logger: WinstonLogger } = await import('./impl/winston');
|
||||
|
||||
return WinstonLogger;
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Configure Winston with OpenTelemetry
|
||||
|
||||
Follow the [SigNoz Winston integration guide](https://signoz.io/docs/logs-management/send-logs/nodejs-winston-logs/) to configure the OpenTelemetry transport for Winston.
|
||||
|
||||
Example Winston configuration:
|
||||
|
||||
```typescript title="packages/shared/src/logger/impl/winston.ts"
|
||||
import winston from 'winston';
|
||||
|
||||
const { combine, timestamp, json } = winston.format;
|
||||
|
||||
export const Logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: combine(
|
||||
timestamp(),
|
||||
json()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console(),
|
||||
// Add OpenTelemetry transport for SigNoz
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## What SigNoz Captures
|
||||
|
||||
### Traces
|
||||
|
||||
SigNoz captures distributed traces across your application:
|
||||
|
||||
- HTTP request traces
|
||||
- Database query traces
|
||||
- External API call traces
|
||||
- Server Component render times
|
||||
|
||||
View traces in SigNoz under **Traces** to see the full request lifecycle.
|
||||
|
||||
### Metrics
|
||||
|
||||
OpenTelemetry metrics are automatically collected:
|
||||
|
||||
- Request duration histograms
|
||||
- Error rates
|
||||
- Request counts by endpoint
|
||||
|
||||
### Logs
|
||||
|
||||
When configured with Winston, logs flow to SigNoz:
|
||||
|
||||
- Application logs at all levels (debug, info, warn, error)
|
||||
- Correlated with traces via trace IDs
|
||||
- Searchable and filterable
|
||||
|
||||
### Exceptions
|
||||
|
||||
Errors captured via the monitoring service appear in SigNoz with:
|
||||
|
||||
- Stack traces
|
||||
- Request context
|
||||
- Correlation with traces and logs
|
||||
|
||||
## SigNoz vs Sentry
|
||||
|
||||
| Feature | SigNoz | Sentry |
|
||||
|---------|--------|--------|
|
||||
| Self-hostable | Yes (primary use case) | Yes (limited) |
|
||||
| OpenTelemetry native | Yes | No |
|
||||
| Traces | Yes | Yes (via APM) |
|
||||
| Logs | Yes | No |
|
||||
| Metrics | Yes | No |
|
||||
| Error tracking | Yes | Yes (primary focus) |
|
||||
| Session replay | No | Yes |
|
||||
| Source maps | Limited | Full support |
|
||||
| Pricing | Free (self-hosted) | Usage-based |
|
||||
|
||||
**Choose SigNoz when:**
|
||||
|
||||
- You want to self-host your observability stack
|
||||
- You prefer OpenTelemetry standards
|
||||
- You need traces, logs, and metrics in one place
|
||||
- You want predictable costs (self-hosted = infrastructure cost only)
|
||||
|
||||
**Choose Sentry when:**
|
||||
|
||||
- You want managed service with minimal setup
|
||||
- You need advanced error tracking features
|
||||
- You want session replay
|
||||
- You prefer detailed source map integration
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Traces not appearing
|
||||
|
||||
1. **Check OTLP endpoints**: Verify all `OTEL_EXPORTER_OTLP_*` variables are set
|
||||
2. **Check connectivity**: Ensure your app can reach the SigNoz endpoints
|
||||
3. **Check the SigNoz logs**: Look for ingestion errors in SigNoz container logs
|
||||
|
||||
### Logs not appearing
|
||||
|
||||
1. **Verify Winston is configured**: Check `LOGGER=winston` is set
|
||||
2. **Check the OpenTelemetry transport**: Ensure the Winston transport is correctly configured
|
||||
3. **Check log levels**: Verify your log level includes the logs you expect
|
||||
|
||||
### Authentication errors
|
||||
|
||||
1. **Check ingestion key**: Verify `signoz-ingestion-key` header is set correctly
|
||||
2. **Check key format**: The header value should be just the key, not `Bearer key`
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [SigNoz documentation](https://signoz.io/docs/)
|
||||
- [OpenTelemetry SDK documentation](https://opentelemetry.io/docs/languages/js/)
|
||||
- [SigNoz GitHub repository](https://github.com/SigNoz/signoz)
|
||||
- [Return to monitoring overview](/docs/next-supabase-turbo/monitoring/overview)
|
||||
Reference in New Issue
Block a user