Adding Sentry is basically done now. We have a nice lil component to help us get started to!

This commit is contained in:
2025-06-07 14:30:25 -05:00
parent eebc022928
commit 23efcdee80
16 changed files with 3538 additions and 1207 deletions

View File

@ -11,7 +11,6 @@
#SKIP_ENV_VALIDATION= # false #SKIP_ENV_VALIDATION= # false
#NEXT_RUNTIME= # nodejs #NEXT_RUNTIME= # nodejs
# Sentry Variables # Default Values: # Sentry Variables # Default Values:
#SENTRY_URL= # https://sentry.gbrown.org
SENTRY_AUTH_TOKEN= SENTRY_AUTH_TOKEN=
#CI= # true #CI= # true
@ -23,6 +22,7 @@ NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY= NEXT_PUBLIC_SUPABASE_ANON_KEY=
# Sentry Variables # Sentry Variables
NEXT_PUBLIC_SENTRY_DSN= NEXT_PUBLIC_SENTRY_DSN=
NEXT_PUBLIC_SENTRY_URL= # https://sentry.gbrown.org
### Script Variables ### These variables are only needed for our scripts, so do not add these to env.js! ### ### Script Variables ### These variables are only needed for our scripts, so do not add these to env.js! ###
# generateTypes # Default Values: # generateTypes # Default Values:

2
.gitignore vendored
View File

@ -44,3 +44,5 @@ yarn-error.log*
# idea files # idea files
.idea .idea
# Sentry Config File
.env.sentry-build-plugin

View File

@ -7,14 +7,6 @@ import { withSentryConfig } from '@sentry/nextjs';
/** @type {import("next").NextConfig} */ /** @type {import("next").NextConfig} */
const config = { const config = {
turbopack: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
output: 'standalone', output: 'standalone',
images: { images: {
remotePatterns: [ remotePatterns: [
@ -24,11 +16,20 @@ const config = {
}, },
], ],
}, },
serverExternalPackages: ['require-in-the-middle'],
experimental: { experimental: {
serverActions: { serverActions: {
bodySizeLimit: '10mb', bodySizeLimit: '10mb',
}, },
}, },
//turbopack: {
//rules: {
//'*.svg': {
//loaders: ['@svgr/webpack'],
//as: '*.js',
//},
//},
//},
}; };
const sentryConfig = { const sentryConfig = {
@ -36,7 +37,7 @@ const sentryConfig = {
// https://www.npmjs.com/package/@sentry/webpack-plugin#options // https://www.npmjs.com/package/@sentry/webpack-plugin#options
org: 'gib', org: 'gib',
project: 't3-supabase-template', project: 't3-supabase-template',
sentryUrl: process.env.SENTRY_URL, sentryUrl: process.env.NEXT_PUBLIC_SENTRY_URL,
authToken: process.env.SENTRY_AUTH_TOKEN, authToken: process.env.SENTRY_AUTH_TOKEN,
// Only print logs for uploading source maps in CI // Only print logs for uploading source maps in CI
silent: !process.env.CI, silent: !process.env.CI,

View File

@ -6,7 +6,8 @@
"scripts": { "scripts": {
"build": "next build", "build": "next build",
"check": "next lint && tsc --noEmit", "check": "next lint && tsc --noEmit",
"dev": "next dev --turbo", "dev": "next dev",
"dev:turbo": "next dev --turbo",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", "format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"lint": "next lint", "lint": "next lint",
@ -16,7 +17,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.0.1", "@hookform/resolvers": "^5.1.0",
"@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-dropdown-menu": "^2.1.15",
@ -42,8 +43,8 @@
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@tailwindcss/postcss": "^4.1.8", "@tailwindcss/postcss": "^4.1.8",
"@types/cors": "^2.8.18", "@types/cors": "^2.8.19",
"@types/express": "^5.0.2", "@types/express": "^5.0.3",
"@types/node": "^20.19.0", "@types/node": "^20.19.0",
"@types/react": "^19.1.6", "@types/react": "^19.1.6",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.1.6",

3533
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
* for Docker builds. * for Docker builds.
*/ */
import './src/env.js'; import './src/env.js';
import { withSentryConfig } from '@sentry/nextjs';
/** @type {import("next").NextConfig} */ /** @type {import("next").NextConfig} */
const config = { const config = {
@ -15,6 +16,7 @@ const config = {
}, },
], ],
}, },
serverExternalPackages: ['require-in-the-middle'],
experimental: { experimental: {
serverActions: { serverActions: {
bodySizeLimit: '10mb', bodySizeLimit: '10mb',
@ -26,6 +28,40 @@ const config = {
eslint: { eslint: {
ignoreDuringBuilds: true, ignoreDuringBuilds: true,
}, },
//turbopack: {
//rules: {
//'*.svg': {
//loaders: ['@svgr/webpack'],
//as: '*.js',
//},
//},
//},
}; };
export default config; const sentryConfig = {
// For all available options, see:
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
org: 'gib',
project: 't3-supabase-template',
sentryUrl: process.env.SENTRY_URL,
authToken: process.env.SENTRY_AUTH_TOKEN,
// Only print logs for uploading source maps in CI
silent: !process.env.CI,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
// This can increase your server load as well as your hosting bill.
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
// side errors will fail.
tunnelRoute: '/monitoring',
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Capture React Component Names
reactComponentAnnotation: {
enabled: true,
},
};
export default withSentryConfig(config, sentryConfig);

View File

@ -3,6 +3,7 @@
* for Docker builds. * for Docker builds.
*/ */
import './src/env.js'; import './src/env.js';
import { withSentryConfig } from '@sentry/nextjs';
/** @type {import("next").NextConfig} */ /** @type {import("next").NextConfig} */
const config = { const config = {
@ -15,11 +16,46 @@ const config = {
}, },
], ],
}, },
serverExternalPackages: ['require-in-the-middle'],
experimental: { experimental: {
serverActions: { serverActions: {
bodySizeLimit: '10mb', bodySizeLimit: '10mb',
}, },
}, },
//turbopack: {
//rules: {
//'*.svg': {
//loaders: ['@svgr/webpack'],
//as: '*.js',
//},
//},
//},
}; };
export default config; const sentryConfig = {
// For all available options, see:
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
org: 'gib',
project: 't3-supabase-template',
sentryUrl: process.env.SENTRY_URL,
authToken: process.env.SENTRY_AUTH_TOKEN,
// Only print logs for uploading source maps in CI
silent: !process.env.CI,
// For all available options, see:
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
// This can increase your server load as well as your hosting bill.
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
// side errors will fail.
tunnelRoute: '/monitoring',
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Capture React Component Names
reactComponentAnnotation: {
enabled: true,
},
};
export default withSentryConfig(config, sentryConfig);

View File

@ -1,23 +1,16 @@
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
// The config you add here will be used whenever one of the edge features is loaded.
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from '@sentry/nextjs';
Sentry.init({ Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN!, dsn: 'https://0468176d5291bc2b914261147bfef117@sentry.gbrown.org/6',
// Adds request headers and IP for users, for more info visit: // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii tracesSampleRate: 1,
sendDefaultPii: true,
// Set tracesSampleRate to 1.0 to capture 100% // Setting this option to true will print useful information to the console while you're setting up Sentry.
// of transactions for tracing. debug: false,
// We recommend adjusting this value in production
// Learn more at
// https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate
tracesSampleRate: 1.0,
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
}); });

View File

@ -1,23 +1,15 @@
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ // This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
Sentry.init({ Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN!, dsn: 'https://0468176d5291bc2b914261147bfef117@sentry.gbrown.org/6',
// Adds request headers and IP for users, for more info visit: // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii tracesSampleRate: 1,
sendDefaultPii: true,
// Set tracesSampleRate to 1.0 to capture 100% // Setting this option to true will print useful information to the console while you're setting up Sentry.
// of transactions for tracing. debug: false,
// We recommend adjusting this value in production
// Learn more at
// https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate
tracesSampleRate: 1.0,
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
}); });

View File

@ -0,0 +1,16 @@
import { NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';
class SentryExampleAPIError extends Error {
constructor(message: string | undefined) {
super(message);
this.name = 'SentryExampleAPIError';
}
}
// A faulty API route to test Sentry's error monitoring
export function GET() {
throw new SentryExampleAPIError(
'This error is raised on the backend called by the example page.',
);
return NextResponse.json({ data: 'Testing Sentry Error...' });
}

View File

@ -4,14 +4,51 @@ import { FetchDataSteps } from '@/components/default/tutorial';
import { InfoIcon } from 'lucide-react'; import { InfoIcon } from 'lucide-react';
import { getUser } from '@/lib/actions'; import { getUser } from '@/lib/actions';
import type { User } from '@/utils/supabase'; import type { User } from '@/utils/supabase';
import Link from 'next/link';
import { TestSentryCard } from '@/components/default/sentry';
import {
Button,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Separator,
} from '@/components/ui';
const HomePage = async () => { const HomePage = async () => {
const response = await getUser(); const response = await getUser();
if (!response.success || !response.data) { if (!response.success || !response.data) {
return ( return (
<main className='w-full items-center justify-center'> <main className='w-full items-center justify-center'>
<div className='flex p-5 items-center justify-center'> <div className='flex flex-col p-5 items-center justify-center space-y-6'>
<h1>Make sure you can sign in!</h1> <Card className='md:min-w-2xl'>
<CardHeader className='flex flex-col items-center'>
<CardTitle className='text-3xl'>
Welcome to the T3 Supabase Template!
</CardTitle>
<CardDescription className='text-[1.0rem] mb-2'>
A great place to start is by creating a new user account &
ensuring you can sign in!
</CardDescription>
<Button
asChild
size='sm'
variant={'default'}
className='w-1/3 items-center'
>
<Link href='/sign-up'>Sign up</Link>
</Button>
</CardHeader>
<Separator className='bg-accent' />
<CardContent className='flex flex-col px-5 py-2 items-center justify-center'>
<CardTitle className='text-lg mb-6 w-2/3 text-center'>
You can also test out your connection to Sentry if you want to
start there!
</CardTitle>
<TestSentryCard />
</CardContent>
</Card>
</div> </div>
</main> </main>
); );
@ -30,14 +67,15 @@ const HomePage = async () => {
</div> </div>
</div> </div>
<div className='flex flex-col gap-2 items-start'> <div className='flex flex-col gap-2 items-start'>
<h2 className='font-bold text-2xl mb-4'>Your user details</h2> <h2 className='font-bold text-3xl mb-4'>Your user details</h2>
<pre <pre
className='text-xs font-mono p-3 rounded className='text-sm font-mono p-3 rounded
border max-h-32 overflow-auto' border max-h-50 overflow-auto'
> >
{JSON.stringify(user, null, 2)} {JSON.stringify(user, null, 2)}
</pre> </pre>
</div> </div>
<TestSentryCard />
<div> <div>
<h2 className='font-bold text-2xl mb-4'>Next steps</h2> <h2 className='font-bold text-2xl mb-4'>Next steps</h2>
<FetchDataSteps /> <FetchDataSteps />

View File

@ -0,0 +1,125 @@
'use client';
import * as Sentry from '@sentry/nextjs';
import { useState, useEffect } from 'react';
import {
Button,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Separator,
} from '@/components/ui';
import Link from 'next/link';
import { CheckCircle, MessageCircleWarning } from 'lucide-react';
class SentryExampleFrontendError extends Error {
constructor(message: string | undefined) {
super(message);
this.name = 'SentryExampleFrontendError';
}
}
export const TestSentryCard = () => {
const [hasSentError, setHasSentError] = useState(false);
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
const checkConnectivity = async () => {
const result = await Sentry.diagnoseSdkConnectivity();
setIsConnected(result !== 'sentry-unreachable');
};
checkConnectivity().catch((error) => {
console.error('Error trying to connect to Sentry: ', error);
});
}, []);
const createError = async () => {
await Sentry.startSpan(
{
name: 'Example Frontend Span',
op: 'test',
},
async () => {
const res = await fetch('/api/sentry/example');
if (!res.ok) {
setHasSentError(true);
throw new SentryExampleFrontendError(
'This error is raised in our TestSentry component on the main page.',
);
}
},
);
};
return (
<Card>
<CardHeader>
<div className='flex flex-row my-auto space-x-4'>
<svg
height='40'
width='40'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
d='M21.85 2.995a3.698 3.698 0 0 1 1.353 1.354l16.303 28.278a3.703 3.703 0 0 1-1.354 5.053 3.694 3.694 0 0 1-1.848.496h-3.828a31.149 31.149 0 0 0 0-3.09h3.815a.61.61 0 0 0 .537-.917L20.523 5.893a.61.61 0 0 0-1.057 0l-3.739 6.494a28.948 28.948 0 0 1 9.63 10.453 28.988 28.988 0 0 1 3.499 13.78v1.542h-9.852v-1.544a19.106 19.106 0 0 0-2.182-8.85 19.08 19.08 0 0 0-6.032-6.829l-1.85 3.208a15.377 15.377 0 0 1 6.382 12.484v1.542H3.696A3.694 3.694 0 0 1 0 34.473c0-.648.17-1.286.494-1.849l2.33-4.074a8.562 8.562 0 0 1 2.689 1.536L3.158 34.17a.611.611 0 0 0 .538.917h8.448a12.481 12.481 0 0 0-6.037-9.09l-1.344-.772 4.908-8.545 1.344.77a22.16 22.16 0 0 1 7.705 7.444 22.193 22.193 0 0 1 3.316 10.193h3.699a25.892 25.892 0 0 0-3.811-12.033 25.856 25.856 0 0 0-9.046-8.796l-1.344-.772 5.269-9.136a3.698 3.698 0 0 1 3.2-1.849c.648 0 1.285.17 1.847.495Z'
fill='currentcolor'
/>
</svg>
<CardTitle className='text-3xl my-auto'>Test Sentry</CardTitle>
</div>
<CardDescription className='text-[1.0rem]'>
Click the button below & view the sample error on{' '}
<Link
href={`${process.env.NEXT_PUBLIC_SENTRY_URL}`}
className='text-accent-foreground underline hover:text-primary'
>
the Sentry website
</Link>
. Navigate to the {"'"}Issues{"'"} page & you should see the sample
error!
</CardDescription>
</CardHeader>
<CardContent>
<div className='flex flex-row gap-4 my-auto'>
<Button
type='button'
onClick={createError}
className='cursor-pointer text-md my-auto py-6'
>
<span>Throw Sample Error</span>
</Button>
{hasSentError ? (
<div className='rounded-md bg-green-500/80 dark:bg-green-500/50 py-2 px-4 flex flex-row gap-2 my-auto'>
<CheckCircle size={30} className='my-auto' />
<p className='text-lg'>Sample error was sent to Sentry!</p>
</div>
) : !isConnected ? (
<div className='rounded-md bg-red-600/50 dark:bg-red-500/50 py-2 px-4 flex flex-row gap-2 my-auto'>
<MessageCircleWarning size={40} className='my-auto' />
<p>
Wait! The Sentry SDK is not able to reach Sentry right now -
this may be due to an adblocker. For more information, see{' '}
<Link
href='https://docs.sentry.io/platforms/javascript/guides/nextjs/troubleshooting/#the-sdk-is-not-sending-any-data'
className='text-accent-foreground underline hover:text-primary'
>
the troubleshooting guide.
</Link>
</p>
</div>
) : (
<div className='success_placeholder' />
)}
</div>
<Separator className='my-4 bg-accent' />
<p className='description'>
Warning! Sometimes Adblockers will prevent errors from being sent to
Sentry.
</p>
</CardContent>
</Card>
);
};

View File

@ -0,0 +1 @@
export { TestSentryCard } from './TestSentry';

View File

@ -7,11 +7,12 @@ export const env = createEnv({
* This way you can ensure the app isn't built with invalid env vars. * This way you can ensure the app isn't built with invalid env vars.
*/ */
server: { server: {
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), NODE_ENV: z
.enum(['development', 'test', 'production'])
.default('development'),
NEXT_RUNTIME: z.enum(['nodejs', 'edge']).default('nodejs'), NEXT_RUNTIME: z.enum(['nodejs', 'edge']).default('nodejs'),
SENTRY_URL: z.string().url().default('https://sentry.gbrown.org'),
SENTRY_AUTH_TOKEN: z.string().min(1), SENTRY_AUTH_TOKEN: z.string().min(1),
CI: z.enum(['true', 'false']).default('true'), CI: z.enum(['true', 'false']).default('false'),
}, },
/** /**
@ -24,6 +25,10 @@ export const env = createEnv({
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1), NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
NEXT_PUBLIC_SITE_URL: z.string().url().default('http://localhost:3000'), NEXT_PUBLIC_SITE_URL: z.string().url().default('http://localhost:3000'),
NEXT_PUBLIC_SENTRY_DSN: z.string().min(1), NEXT_PUBLIC_SENTRY_DSN: z.string().min(1),
NEXT_PUBLIC_SENTRY_URL: z
.string()
.url()
.default('https://sentry.gbrown.org'),
}, },
/** /**
@ -33,7 +38,6 @@ export const env = createEnv({
runtimeEnv: { runtimeEnv: {
NODE_ENV: process.env.NODE_ENV, NODE_ENV: process.env.NODE_ENV,
NEXT_RUNTIME: process.env.NEXT_RUNTIME, NEXT_RUNTIME: process.env.NEXT_RUNTIME,
SENTRY_URL: process.env.SENTRY_URL,
SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN, SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN,
CI: process.env.CI, CI: process.env.CI,
@ -41,6 +45,7 @@ export const env = createEnv({
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL, NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
NEXT_PUBLIC_SENTRY_URL: process.env.NEXT_PUBLIC_SENTRY_URL,
}, },
/** /**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially

View File

@ -1,6 +1,7 @@
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ // This file configures the initialization of Sentry on the client.
// The added config here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
import './src/env.js';
Sentry.init({ Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN!, dsn: process.env.NEXT_PUBLIC_SENTRY_DSN!,

View File

@ -1,7 +1,6 @@
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
import type { Instrumentation } from 'next'; import type { Instrumentation } from 'next';
export const register = async () => { export const register = async () => {
if (process.env.NEXT_RUNTIME === 'edge') { if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config'); await import('../sentry.edge.config');