/** * Canonical Sentry wiring snippets the AI should drop into any * new app it scaffolds. Keeping these in one place (and naming * the file paths explicitly) means the AI's outputs are * deterministic across chats — every Next.js app it scaffolds * gets the same instrumentation files, the same global error * boundary, and the same `withSentryConfig` wrapper. * * The runtime env vars `NEXT_PUBLIC_SENTRY_DSN` and * `SENTRY_AUTH_TOKEN` are guaranteed to exist on every Coolify * app created via apps.create with a `projectId` — see * `lib/integrations/sentry.ts` and `applyEnvsAndDeploy` in * `app/api/mcp/route.ts`. * * The AI references these via `getSentrySnippets("nextjs")` etc. * Authoring rule: when changing any snippet, also bump the * matching file in vibn-frontend itself so they stay in sync — * vibn-frontend is the reference implementation. */ export type SentryFramework = 'nextjs' | 'vite-react'; export interface SentrySnippet { /** Repo-relative path the file should land at. */ path: string; /** File contents, copied verbatim by the AI. */ contents: string; /** Short description for AI to mention to the user, if relevant. */ purpose: string; } export interface SentryWiringPackage { framework: SentryFramework; /** npm dependencies the AI must add to package.json. */ dependencies: string[]; /** Files to write into the project. */ files: SentrySnippet[]; /** Free-form modifications the AI must apply to existing files. */ modifications: string[]; } /** * Returns the full set of files + dependency edits + free-form * modifications the AI must apply when scaffolding a new app of * this framework. Currently covers Next.js (App Router) and * Vite + React. */ export function getSentrySnippets(framework: SentryFramework): SentryWiringPackage { switch (framework) { case 'nextjs': return NEXTJS_PACKAGE; case 'vite-react': return VITE_REACT_PACKAGE; default: { const exhaustive: never = framework; throw new Error(`Unsupported Sentry framework: ${exhaustive}`); } } } // ────────────────────────────────────────────────────────────────── // Next.js (App Router, Next 14+) // ────────────────────────────────────────────────────────────────── const NEXTJS_PACKAGE: SentryWiringPackage = { framework: 'nextjs', dependencies: ['@sentry/nextjs'], files: [ { path: 'instrumentation.ts', purpose: 'Server + edge runtime Sentry init', contents: `import * as Sentry from '@sentry/nextjs'; export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, tracesSampleRate: 1.0, enabled: Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN), environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV, }); } if (process.env.NEXT_RUNTIME === 'edge') { Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, tracesSampleRate: 1.0, enabled: Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN), environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV, }); } } export const onRequestError = Sentry.captureRequestError; `, }, { path: 'instrumentation-client.ts', purpose: 'Browser Sentry init with Session Replay', contents: `import * as Sentry from '@sentry/nextjs'; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, enabled: Boolean(process.env.NEXT_PUBLIC_SENTRY_DSN), environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT || process.env.NODE_ENV, tracesSampleRate: 1.0, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, integrations: [ Sentry.replayIntegration({ maskAllText: true, blockAllMedia: true, }), ], }); export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; `, }, { path: 'app/global-error.tsx', purpose: 'Catches root-layout crashes that escape every other error boundary', contents: `"use client"; import * as Sentry from "@sentry/nextjs"; import { useEffect } from "react"; export default function GlobalError({ error, }: { error: Error & { digest?: string }; }) { useEffect(() => { Sentry.captureException(error); }, [error]); return (

Something went wrong

We've been notified. Try refreshing.

{error.digest ? ref: {error.digest} : null}
); } `, }, ], modifications: [ 'In next.config.ts (or next.config.js), import withSentryConfig from "@sentry/nextjs" and wrap the exported config with withSentryConfig(nextConfig, { org: "vibnai", project: "", silent: !process.env.CI, widenClientFileUpload: true, tunnelRoute: "/monitoring", telemetry: false, errorHandler: (err) => console.warn("Sentry source map upload skipped:", err.message) }).', 'In Dockerfile (if the project uses Docker), add ARG NEXT_PUBLIC_SENTRY_DSN and ARG SENTRY_AUTH_TOKEN in the builder stage, then ENV NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN and ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN — without these the build args from Coolify never reach `next build`.', 'The Sentry project slug for this Vibn project is fs_projects.data.sentry.slug — fetch it from the projects/get MCP tool and substitute into the next.config.ts above.', ], }; // ────────────────────────────────────────────────────────────────── // Vite + React // ────────────────────────────────────────────────────────────────── const VITE_REACT_PACKAGE: SentryWiringPackage = { framework: 'vite-react', dependencies: ['@sentry/react', '@sentry/vite-plugin'], files: [ { path: 'src/sentry.ts', purpose: 'Sentry init, imported once at the top of main.tsx', contents: `import * as Sentry from "@sentry/react"; Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN, enabled: Boolean(import.meta.env.VITE_SENTRY_DSN), environment: import.meta.env.MODE, tracesSampleRate: 1.0, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, integrations: [ Sentry.browserTracingIntegration(), Sentry.replayIntegration({ maskAllText: true, blockAllMedia: true }), ], }); `, }, ], modifications: [ 'In src/main.tsx, add `import "./sentry"` as the FIRST import line (before React, before App).', 'In vite.config.ts, add the Sentry vite plugin: import { sentryVitePlugin } from "@sentry/vite-plugin"; then in plugins: [sentryVitePlugin({ org: "vibnai", project: "", authToken: process.env.SENTRY_AUTH_TOKEN, telemetry: false, errorHandler: (err) => console.warn(err.message) })]. Set build.sourcemap = true so source maps generate.', 'Vite uses VITE_SENTRY_DSN (not NEXT_PUBLIC_SENTRY_DSN). Add `VITE_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN` as a Coolify env var alias on the app, OR have the AI rename the env when injecting via apps_create.', 'Wrap the root in Something broke

}>
so React render errors propagate to Sentry.', ], };