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.
This commit is contained in:
@@ -4,7 +4,10 @@ import type { NextConfig } from 'next';
|
|||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
transpilePackages: ['@nexumi/shared'],
|
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;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Inter } from 'next/font/google';
|
import { Inter } from 'next/font/google';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { Toaster } from 'sonner';
|
|
||||||
import { LocaleProvider } from '@/components/locale-provider';
|
import { LocaleProvider } from '@/components/locale-provider';
|
||||||
import { ThemeProvider } from '@/components/layout/theme-provider';
|
import { ThemeProvider } from '@/components/layout/theme-provider';
|
||||||
|
import { Toaster } from '@/components/ui/toaster';
|
||||||
import { getLocale, MESSAGES } from '@/lib/i18n';
|
import { getLocale, MESSAGES } from '@/lib/i18n';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
|||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<LocaleProvider locale={locale} messages={MESSAGES[locale]}>
|
<LocaleProvider locale={locale} messages={MESSAGES[locale]}>
|
||||||
{children}
|
{children}
|
||||||
<Toaster richColors position="top-right" />
|
<Toaster />
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
7
apps/webui/src/components/ui/toaster.tsx
Normal file
7
apps/webui/src/components/ui/toaster.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Toaster as SonnerToaster } from 'sonner';
|
||||||
|
|
||||||
|
export function Toaster() {
|
||||||
|
return <SonnerToaster richColors position="top-right" />;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
export async function register(): Promise<void> {
|
export async function register(): Promise<void> {
|
||||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||||
const { initWebuiSentry } = await import('./lib/sentry');
|
const { initWebuiSentry } = await import('./lib/sentry');
|
||||||
initWebuiSentry();
|
await initWebuiSentry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { env } from './env';
|
|
||||||
|
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
|
||||||
export function initWebuiSentry(): void {
|
export async function initWebuiSentry(): Promise<void> {
|
||||||
if (initialized || !env.SENTRY_DSN) {
|
if (initialized) {
|
||||||
return;
|
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({
|
Sentry.init({
|
||||||
dsn: env.SENTRY_DSN,
|
dsn,
|
||||||
environment: env.NODE_ENV,
|
environment: process.env.NODE_ENV ?? 'production',
|
||||||
release: 'nexumi-webui@0.1.0',
|
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: {
|
initialScope: {
|
||||||
tags: { service: 'webui' }
|
tags: { service: 'webui' }
|
||||||
}
|
}
|
||||||
@@ -19,10 +25,15 @@ export function initWebuiSentry(): void {
|
|||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function captureWebuiException(error: unknown, context?: Record<string, unknown>): void {
|
export async function captureWebuiException(
|
||||||
if (!env.SENTRY_DSN) {
|
error: unknown,
|
||||||
|
context?: Record<string, unknown>
|
||||||
|
): Promise<void> {
|
||||||
|
const dsn = process.env.SENTRY_DSN?.trim();
|
||||||
|
if (!dsn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const Sentry = await import('@sentry/node');
|
||||||
Sentry.withScope((scope) => {
|
Sentry.withScope((scope) => {
|
||||||
if (context) {
|
if (context) {
|
||||||
scope.setExtras(context);
|
scope.setExtras(context);
|
||||||
|
|||||||
Reference in New Issue
Block a user