From c14c2ddaa2b0ff057a390622382388cd1a2bb9ea Mon Sep 17 00:00:00 2001 From: smueller Date: Thu, 23 Jul 2026 10:03:00 +0200 Subject: [PATCH] Update Next.js configuration and improve Sentry integration - Added `serverExternalPackages` to the Next.js configuration to exclude Node-only packages from the webpack bundle, enhancing compatibility with React's RSC/client boundary. - Refactored Sentry initialization and exception capturing to be asynchronous, ensuring proper handling of the Sentry DSN and preventing potential issues during Next.js builds. - Updated the `Toaster` component import in the layout to improve UI consistency. --- apps/webui/next.config.ts | 5 +++- apps/webui/src/app/layout.tsx | 4 +-- apps/webui/src/components/ui/toaster.tsx | 7 ++++++ apps/webui/src/instrumentation.ts | 2 +- apps/webui/src/lib/sentry.ts | 31 ++++++++++++++++-------- 5 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 apps/webui/src/components/ui/toaster.tsx diff --git a/apps/webui/next.config.ts b/apps/webui/next.config.ts index 1ca2391..f5b3d39 100644 --- a/apps/webui/next.config.ts +++ b/apps/webui/next.config.ts @@ -4,7 +4,10 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { output: 'standalone', transpilePackages: ['@nexumi/shared'], - outputFileTracingRoot: path.join(__dirname, '../..') + outputFileTracingRoot: path.join(__dirname, '../..'), + // Keep Node-only packages out of the Next server webpack bundle so they + // cannot break React's RSC/client boundary during page-data collection. + serverExternalPackages: ['@sentry/node', '@prisma/client', 'ioredis', 'bullmq'] }; export default nextConfig; diff --git a/apps/webui/src/app/layout.tsx b/apps/webui/src/app/layout.tsx index 36df855..8717499 100644 --- a/apps/webui/src/app/layout.tsx +++ b/apps/webui/src/app/layout.tsx @@ -1,9 +1,9 @@ import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import type { ReactNode } from 'react'; -import { Toaster } from 'sonner'; import { LocaleProvider } from '@/components/locale-provider'; import { ThemeProvider } from '@/components/layout/theme-provider'; +import { Toaster } from '@/components/ui/toaster'; import { getLocale, MESSAGES } from '@/lib/i18n'; import './globals.css'; @@ -23,7 +23,7 @@ export default async function RootLayout({ children }: { children: ReactNode }) {children} - + diff --git a/apps/webui/src/components/ui/toaster.tsx b/apps/webui/src/components/ui/toaster.tsx new file mode 100644 index 0000000..afe66ba --- /dev/null +++ b/apps/webui/src/components/ui/toaster.tsx @@ -0,0 +1,7 @@ +'use client'; + +import { Toaster as SonnerToaster } from 'sonner'; + +export function Toaster() { + return ; +} diff --git a/apps/webui/src/instrumentation.ts b/apps/webui/src/instrumentation.ts index 3104663..bce497c 100644 --- a/apps/webui/src/instrumentation.ts +++ b/apps/webui/src/instrumentation.ts @@ -1,6 +1,6 @@ export async function register(): Promise { if (process.env.NEXT_RUNTIME === 'nodejs') { const { initWebuiSentry } = await import('./lib/sentry'); - initWebuiSentry(); + await initWebuiSentry(); } } diff --git a/apps/webui/src/lib/sentry.ts b/apps/webui/src/lib/sentry.ts index efaa77e..a0e11bd 100644 --- a/apps/webui/src/lib/sentry.ts +++ b/apps/webui/src/lib/sentry.ts @@ -1,17 +1,23 @@ -import * as Sentry from '@sentry/node'; -import { env } from './env'; - let initialized = false; -export function initWebuiSentry(): void { - if (initialized || !env.SENTRY_DSN) { +export async function initWebuiSentry(): Promise { + if (initialized) { return; } + const dsn = process.env.SENTRY_DSN?.trim(); + if (!dsn) { + return; + } + + // Import only when a DSN is configured. Loading `@sentry/node` at module + // scope patches Node builtins and can break Next.js page-data collection + // (`createContext is not a function`) during `next build`. + const Sentry = await import('@sentry/node'); Sentry.init({ - dsn: env.SENTRY_DSN, - environment: env.NODE_ENV, + dsn, + environment: process.env.NODE_ENV ?? 'production', release: 'nexumi-webui@0.1.0', - tracesSampleRate: env.NODE_ENV === 'production' ? 0.1 : 1.0, + tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0, initialScope: { tags: { service: 'webui' } } @@ -19,10 +25,15 @@ export function initWebuiSentry(): void { initialized = true; } -export function captureWebuiException(error: unknown, context?: Record): void { - if (!env.SENTRY_DSN) { +export async function captureWebuiException( + error: unknown, + context?: Record +): Promise { + const dsn = process.env.SENTRY_DSN?.trim(); + if (!dsn) { return; } + const Sentry = await import('@sentry/node'); Sentry.withScope((scope) => { if (context) { scope.setExtras(context);