Creating a Custom Monitoring Provider
Integrate LogRocket, Bugsnag, Datadog, or any monitoring service by implementing the MonitoringService interface.
How to create a custom monitoring provider
Add your preferred monitoring service to the kit.
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:
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:
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" }}packages/monitoring/logrocket/src/index.ts
export { LogRocketMonitoringService } from './logrocket-monitoring.service';Register the Provider
Client-Side Registration
Update the monitoring provider registry:
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 registrationmonitoringProviderRegistry.register('sentry', async () => { const { SentryProvider } = await import('@kit/sentry/provider'); return { default: function SentryProviderWrapper({ children }) { return <SentryProvider>{children}</SentryProvider>; }, };});// Add LogRocket registrationmonitoringProviderRegistry.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:
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() { return MONITORING_PROVIDER.parse(process.env.NEXT_PUBLIC_MONITORING_PROVIDER);}Create the Provider Component
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:
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 registrationserverMonitoringRegistry.register('sentry', async () => { const { SentryMonitoringService } = await import('@kit/sentry'); return new SentryMonitoringService();});// Add LogRocket registrationserverMonitoringRegistry.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:
apps/web/.env.local
# Enable LogRocket as the monitoring providerNEXT_PUBLIC_MONITORING_PROVIDER=logrocket# LogRocket configurationNEXT_PUBLIC_LOGROCKET_APP_ID=your-org/your-appExample: Datadog Integration
Here's a complete example for Datadog RUM:
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
- Browser-only initialization - Check
typeof window !== 'undefined'before accessing browser APIs. - Ready state - The
ready()method must resolve after initialization completes. Server contexts callawait service.ready()before capturing. - Provider enum - Remember to add your provider to the
MONITORING_PROVIDERSarray inget-monitoring-provider.ts. - Lazy loading - Providers are loaded lazily through the registry. Don't import the monitoring service directly in your main bundle.
- 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 Drizzle SaaS Kit.
Previous: Sentry Configuration ←