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:
Giancarlo Buomprisco
2026-03-24 13:40:38 +08:00
committed by GitHub
parent 4912e402a3
commit 7ebff31475
840 changed files with 71395 additions and 20095 deletions

View 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)