diff --git a/apps/next/src/app/global-error.tsx b/apps/next/src/app/global-error.tsx new file mode 100644 index 0000000..ff2954e --- /dev/null +++ b/apps/next/src/app/global-error.tsx @@ -0,0 +1,79 @@ +'use client'; + +import type { Metadata, Viewport } from 'next'; +import NextError from 'next/error'; +import { Geist, Geist_Mono } from 'next/font/google'; +import '@/app/styles.css'; +import { useEffect } from 'react'; +import Footer from '@/components/layout/footer'; +import Header from '@/components/layout/header'; +import * as Sentry from '@sentry/nextjs'; +import { ConvexClientProvider } from '@/components/providers'; +import { generateMetadata } from '@/lib/metadata'; +import PlausibleProvider from 'next-plausible'; + +import { Button, ThemeProvider, Toaster } from '@gib/ui'; + +export const metadata: Metadata = generateMetadata(); + +export const viewport: Viewport = { + themeColor: [ + { media: '(prefers-color-scheme: light)', color: 'white' }, + { media: '(prefers-color-scheme: dark)', color: 'black' }, + ], +}; + +const geistSans = Geist({ + subsets: ['latin'], + variable: '--font-geist-sans', +}); +const geistMono = Geist_Mono({ + subsets: ['latin'], + variable: '--font-geist-mono', +}); + +interface GlobalErrorProps { + error: Error & { digest?: string }; + reset?: () => void; +}; + +const GlobalError = ({ error, reset = undefined }: GlobalErrorProps) => { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + return ( + + + + + +
+
+ + {reset !== undefined && ( + + )} + +
+
+
+ +
+
+
+ + +
+ ); +}; +export default GlobalError; diff --git a/apps/next/src/instrumentation-client.ts b/apps/next/src/instrumentation-client.ts new file mode 100644 index 0000000..4abcdfd --- /dev/null +++ b/apps/next/src/instrumentation-client.ts @@ -0,0 +1,28 @@ +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ +import * as Sentry from '@sentry/nextjs'; + +import { env } from './env.js'; + +Sentry.init({ + dsn: env.NEXT_PUBLIC_SENTRY_DSN, + integrations: [ + Sentry.replayIntegration({ + maskAllText: false, + blockAllMedia: false, + }), + Sentry.feedbackIntegration({ + colorScheme: 'system', + }), + ], + // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii + sendDefaultPii: true, + // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate + tracesSampleRate: 1, + enableLogs: true, + // https://docs.sentry.io/platforms/javascript/session-replay/configuration/#general-integration-configuration + replaysSessionSampleRate: 0.5, + replaysOnErrorSampleRate: 1.0, + debug: false, +}); +// `captureRouterTransitionStart` is available from SDK version 9.12.0 onwards +export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; diff --git a/apps/next/src/instrumentation.ts b/apps/next/src/instrumentation.ts new file mode 100644 index 0000000..ff8b9ef --- /dev/null +++ b/apps/next/src/instrumentation.ts @@ -0,0 +1,7 @@ +import type { Instrumentation } from 'next'; +import * as Sentry from '@sentry/nextjs'; + +export const register = async () => await import('./sentry.server.config'); +export const onRequestError: Instrumentation.onRequestError = (...args) => { + Sentry.captureRequestError(...args); +}; diff --git a/apps/next/src/sentry.server.config.ts b/apps/next/src/sentry.server.config.ts new file mode 100644 index 0000000..12dc187 --- /dev/null +++ b/apps/next/src/sentry.server.config.ts @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/nextjs'; + +import { env } from './env.js'; + +Sentry.init({ + dsn: env.NEXT_PUBLIC_SENTRY_DSN, + tracesSampleRate: 1, + enableLogs: true, + debug: false, +}); diff --git a/docker/.dockerignore b/docker/.dockerignore index cb3123f..60138fc 100644 --- a/docker/.dockerignore +++ b/docker/.dockerignore @@ -1,10 +1,13 @@ # Dependencies - exclude node_modules from being copied +node_modules **/node_modules # Turbo +.turbo **/.turbo # Next.js build artifacts +.next **/.next **/out @@ -16,6 +19,7 @@ .env*.local .vscode .idea +.cache # Tests **/__tests__