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.
Five registration points, not three
SDK initialization is no longer owned by MonitoringService. The instrumentation files (instrumentation.ts and instrumentation-client.ts) own SDK init and provider-agnostic global error handlers; the service is a thin capture/identify wrapper. A custom provider has to register in all five registries below — in particular, the onRequestError entry is what keeps server errors from being silently dropped after the error.tsx digest dedup.
Implement the MonitoringService Interface
The service is a thin wrapper around the SDK's capture / identify APIs — no SDK initialization here. Init happens in the instrumentation registrations further down.
packages/monitoring/logrocket/src/logrocket-monitoring.service.ts
import LogRocket from 'logrocket';import { MonitoringService } from '@kit/monitoring-core';export class LogRocketMonitoringService implements MonitoringService { // The interface still requires `ready()`. Init runs in the instrumentation // entrypoint, so this is a no-op for new providers. ready() { return Promise.resolve(); } 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, }); }}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 Type
Update the provider enum so NEXT_PUBLIC_MONITORING_PROVIDER=logrocket validates:
packages/monitoring/api/src/get-monitoring-provider.ts
const MONITORING_PROVIDERS = [ 'sentry', 'logrocket', // Add your provider '',] as const;Register the React Provider
The React provider injects your MonitoringService into the MonitoringContext so useMonitoring / useCaptureException can reach it from client components:
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> );}packages/monitoring/api/src/components/provider.tsx
monitoringProviderRegistry.register('logrocket', async () => { const { LogRocketProvider } = await import('@kit/logrocket/provider'); return { default: function LogRocketProviderWrapper({ children }: React.PropsWithChildren) { return <LogRocketProvider>{children}</LogRocketProvider>; }, };});Register the Server Service
Used anywhere code calls getServerMonitoringService() (Server Actions, Route Handlers, jobs):
packages/monitoring/api/src/services/get-server-monitoring-service.ts
serverMonitoringRegistry.register('logrocket', async () => { const { LogRocketMonitoringService } = await import('@kit/logrocket'); return new LogRocketMonitoringService();});Register the Server Instrumentation
This is the entry that initializes your SDK on the server and forwards Next.js request errors. onRequestError is required — without it, every server error is dropped (the client error.tsx already skips reports for errors with a digest).
packages/monitoring/api/src/instrumentation.ts
instrumentationRegistry.register('logrocket', async () => { const { initializeLogRocketServerClient } = await import( '@kit/logrocket/config/server' ); return { register: () => { initializeLogRocketServerClient({ appId: process.env.LOGROCKET_APP_ID, }); }, onRequestError: async (error, request, context) => { const { getServerMonitoringService } = await import( '../services/get-server-monitoring-service' ); const service = await getServerMonitoringService(); await service.captureException(error as Error, {}, { path: request.path, headers: request.headers, method: request.method, routePath: context.routePath, }); }, };});If your SDK provides a built-in Next.js request-error handler (the way @sentry/nextjs exposes captureRequestError), use it directly — it will produce richer context than a hand-rolled forward.
Register the Client Instrumentation
The client instrumentation entry initializes the browser SDK and provides a captureException that the provider-agnostic global handlers in instrumentation-client.ts will call. Do not register your own window.onerror / unhandledrejection listeners — the monitoring layer owns those, and adding more will double-report. If your SDK installs its own by default (e.g. Sentry's GlobalHandlers), disable that integration.
packages/monitoring/api/src/instrumentation-client.ts
clientInstrumentationRegistry.register('logrocket', async () => { const [{ initializeLogRocketBrowserClient }, LogRocket] = await Promise.all([ import('@kit/logrocket/config/client'), import('logrocket'), ]); return { init: () => { initializeLogRocketBrowserClient({ appId: process.env.NEXT_PUBLIC_LOGROCKET_APP_ID, }); }, captureException: (error, mechanism) => { LogRocket.captureException(error as Error, { tags: { mechanism }, }); }, };});The mechanism argument is one of onerror, onunhandledrejection, or react-error-boundary — useful for telling unhandled rejections from React boundary errors in your dashboard.
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. The service is a thin wrapper; init lives in the client instrumentation entry below.
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 { ready() { return Promise.resolve(); } 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, }); }}packages/monitoring/datadog/src/datadog.client.config.ts
import { datadogRum } from '@datadog/browser-rum';export function initializeDatadogBrowserClient() { 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, // Datadog can capture its own unhandled errors; turn it off because the // monitoring layer already installs `window.onerror` / `unhandledrejection`. forwardErrorsToLogs: false, });}Then wire the client instrumentation and React provider as shown in the registration sections above.
Common Gotchas
- Don't double-install global handlers —
@kit/monitoring/instrumentation-clientownswindow.onerrorandunhandledrejection. Disable any equivalent feature in your SDK's options (Sentry'sGlobalHandlers, Datadog'sforwardErrorsToLogs, etc.). onRequestErroris required — the client error boundary skips reports for errors that carry adigest. Without a server-sideonRequestError, those errors disappear.- Don't init from the service constructor — initialization is owned by
instrumentation.ts/instrumentation-client.ts. The service should be stateless. - Provider enum — every new provider must be added to
MONITORING_PROVIDERSinget-monitoring-provider.ts, otherwise Zod will reject the env var. - Lazy loading — providers are loaded lazily through registries. Don't statically import provider SDKs from shared code, or you'll pull them into every bundle.
- Browser-only SDKs — providers like LogRocket can't run on the server. Either guard the server registration (return a no-op service) or skip the registration entirely.
This monitoring system is part of the Next.js Supabase SaaS Kit.
Previous: Sentry Configuration ←