Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable sentry features #6133

Merged
merged 11 commits into from
Nov 23, 2023
18 changes: 18 additions & 0 deletions app/[locale]/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client';

import type { FC } from 'react';

const ErrorPage: FC<{ error: Error }> = ({ error }) => (
<div className="container">
<h2>500: Internal Server Error</h2>
<h3>This Page has thrown a non-recoverable Error</h3>
<small>
Details: <br />
<pre>
<code>${error.message}</code>
ovflowd marked this conversation as resolved.
Show resolved Hide resolved
</pre>
</small>
</div>
);

export default ErrorPage;
2 changes: 1 addition & 1 deletion app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const RootLayout: FC<PropsWithChildren> = ({ children }) => {

return (
<html className={sourceSans.className} dir={langDir} lang={hrefLang}>
<body>
<body suppressHydrationWarning>
ovflowd marked this conversation as resolved.
Show resolved Hide resolved
<LocaleProvider>
<ThemeProvider>
<BaseLayout>{children}</BaseLayout>
Expand Down
6 changes: 5 additions & 1 deletion components/withNodeRelease.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ export const WithNodeRelease: FC<WithNodeReleaseProps> = ({
[status].flat().includes(release.status)
);

return <Component release={matchingRelease!} />;
if (matchingRelease !== undefined) {
return <Component release={matchingRelease!} />;
}

return null;
};
2 changes: 2 additions & 0 deletions layouts/BaseLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import type { FC, PropsWithChildren } from 'react';

import Footer from '@/components/Footer';
Expand Down
2 changes: 0 additions & 2 deletions next-data/generateNodeReleasesJson.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ const generateNodeReleasesJson = async () => {
};
});

console.timeEnd('g');

return writeFile(
jsonFilePath,
JSON.stringify(
Expand Down
16 changes: 12 additions & 4 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import { withSentryConfig } from '@sentry/nextjs';
import withNextIntl from 'next-intl/plugin';
import webpack from 'webpack';

import {
BASE_PATH,
ENABLE_STATIC_EXPORT,
SENTRY_DSN,
SENTRY_ENABLE,
SENTRY_EXTENSIONS,
SENTRY_TUNNEL,
} from './next.constants.mjs';
import { redirects, rewrites } from './next.rewrites.mjs';

Expand Down Expand Up @@ -44,15 +47,20 @@ const nextConfig = {
// as we already check it on the CI within each Pull Request
// we also configure ESLint to run its lint checking on all files (next lint)
eslint: { dirs: ['.'], ignoreDuringBuilds: true },
// Next.js WebPack Bundler does not know how to handle `.mjs` files on `node_modules`
// This is not an issue when using TurboPack as it uses SWC and it is ESM-only
// Once Next.js uses Turbopack for their build process we can remove this
// Adds custom WebPack configuration to our Next.hs setup
webpack: function (config) {
// Next.js WebPack Bundler does not know how to handle `.mjs` files on `node_modules`
// This is not an issue when using TurboPack as it uses SWC and it is ESM-only
// Once Next.js uses Turbopack for their build process we can remove this
config.module.rules.push({
test: /\.m?js$/,
type: 'javascript/auto',
resolve: { fullySpecified: false },
});

// Tree-shakes modules from Sentry Bundle
config.plugins.push(new webpack.DefinePlugin(SENTRY_EXTENSIONS));

return config;
},
experimental: {
Expand Down Expand Up @@ -92,7 +100,7 @@ const sentryConfig = {
// Upload Next.js or third-party code in addition to our code
widenClientFileUpload: true,
// Attempt to circumvent ad blockers
tunnelRoute: !ENABLE_STATIC_EXPORT && '/monitoring',
tunnelRoute: SENTRY_TUNNEL(),
// Prevent source map comments in built files
hideSourceMaps: false,
// Tree shake Sentry stuff from the bundle
Expand Down
27 changes: 27 additions & 0 deletions next.constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,30 @@ export const SENTRY_DSN =
*/
export const SENTRY_ENABLE =
process.env.NODE_ENV === 'development' || !!VERCEL_ENV;

/**
* This configures the sampling rate for Sentry
*
* @note we always want to capture 100% on preview branches and development mode
ovflowd marked this conversation as resolved.
Show resolved Hide resolved
*/
export const SENTRY_CAPTURE_RATE =
SENTRY_ENABLE && BASE_URL !== 'https://nodejs.org' ? 1.0 : 0.01;

/**
* Provides the Route for Sentry's Server-Side Tunnel
*/
export const SENTRY_TUNNEL = (components = '') =>
SENTRY_ENABLE ? `/monitoring${components}` : undefined;

/**
* This configures which Sentry features to tree-shake/remove from the Sentry bundle
*
* @see https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/tree-shaking/
*/
export const SENTRY_EXTENSIONS = {
__SENTRY_DEBUG__: false,
__SENTRY_TRACING__: false,
__RRWEB_EXCLUDE_IFRAME__: true,
__RRWEB_EXCLUDE_SHADOW_DOM__: true,
__SENTRY_EXCLUDE_REPLAY_WORKER__: true,
};
83 changes: 67 additions & 16 deletions sentry.client.config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,68 @@
import { SENTRY_DSN, SENTRY_ENABLE } from '@/next.constants.mjs';
import {
Dedupe,
Breadcrumbs,
HttpContext,
LinkedErrors,
BrowserClient,
getCurrentHub,
defaultStackParser,
makeFetchTransport,
} from '@sentry/nextjs';

// This lazy-loads Sentry on the Browser
import('@sentry/nextjs').then(({ init }) =>
init({
// Only run Sentry on Vercel Environment
enabled: SENTRY_ENABLE,
// Tell Sentry where to send events
dsn: SENTRY_DSN,
// Disable Sentry Tracing as we don't need to have it
// as Vercel already does Web Vitals and Performance Measurement on Client-Side
enableTracing: false,
// We only want to capture errors from _next folder on production
// We don't want to capture errors from preview branches here
allowUrls: ['https://nodejs.org/_next'],
})
);
import {
SENTRY_DSN,
SENTRY_ENABLE,
SENTRY_CAPTURE_RATE,
SENTRY_TUNNEL,
} from '@/next.constants.mjs';

// This creates a custom Sentry Client with minimal integrations
export const sentryClient = new BrowserClient({
// Only run Sentry on Vercel Environment
enabled: SENTRY_ENABLE,
// Provide Sentry's Secret Key
dsn: SENTRY_DSN,
// Sentry's Error Transport Mechanism
transport: makeFetchTransport,
// Sentry's Stack Trace Parser
stackParser: defaultStackParser,
// All supported Integrations by us
integrations: [
new Dedupe(),
new HttpContext(),
new Breadcrumbs(),
new LinkedErrors(),
],
// We only want to capture errors from _next folder on production
// We don't want to capture errors from preview branches here
allowUrls: ['https://nodejs.org/', /^https:\/\/.+\.vercel\.app/],
// Percentage of events to send to Sentry (1% of them) (for performance metrics)
ovflowd marked this conversation as resolved.
Show resolved Hide resolved
tracesSampleRate: SENTRY_CAPTURE_RATE,
// Percentage of events to send to Sentry (1% of them) (for session replays)
replaysSessionSampleRate: SENTRY_CAPTURE_RATE,
// Percentage of events to send to Sentry (1% of them) (for session replays when error happens)
replaysOnErrorSampleRate: 1.0,
// Provides a custom Sentry Tunnel Router
// @note these are components of the Sentry DSN string
// @see @sentry/nextjs/esm/client/tunnelRoute.js
tunnel: SENTRY_TUNNEL(`?o=4506191161786368&p=4506191307735040`),
// Adds custom filtering before sending an Event to Sentry
beforeSend: (event, hint) => {
// Attempts to grab the original Exception before any "magic" happens
const exception = hint.originalException as Error;

// We only want to capture Errors that have a Stack Trace and that are not Anonymous Errors
return exception?.stack && !exception.stack.includes('<anonymous>')
? event
: null;
},
});

// Attaches this Browser Client to Sentry
getCurrentHub().bindClient(sentryClient);

// Loads this Dynamically to avoid adding this to the main bundle (initial load)
import('@sentry/nextjs').then(({ Replay, BrowserTracing }) => {
sentryClient.addIntegration(new Replay({ maskAllText: false }));
sentryClient.addIntegration(new BrowserTracing());
});
2 changes: 1 addition & 1 deletion sentry.edge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SENTRY_DSN, SENTRY_ENABLE } from '@/next.constants.mjs';
init({
// Only run Sentry on Vercel Environment
enabled: SENTRY_ENABLE,
// Tell Sentry where to send events
// Provide Sentry's Secret Key
dsn: SENTRY_DSN,
// Percentage of events to send to Sentry (1% of them) (for performance metrics)
tracesSampleRate: 0.01,
Expand Down
10 changes: 7 additions & 3 deletions sentry.server.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { init } from '@sentry/nextjs';
import { ProfilingIntegration } from '@sentry/profiling-node';

import { SENTRY_DSN, SENTRY_ENABLE } from '@/next.constants.mjs';
import {
SENTRY_DSN,
SENTRY_ENABLE,
SENTRY_CAPTURE_RATE,
} from '@/next.constants.mjs';

init({
// Only run Sentry on Vercel Environment
enabled: SENTRY_ENABLE,
// Tell Sentry where to send events
// Provide Sentry's Secret Key
dsn: SENTRY_DSN,
// Percentage of events to send to Sentry (1% of them) (for performance metrics)
tracesSampleRate: 0.01,
tracesSampleRate: SENTRY_CAPTURE_RATE,
// Percentage of events to send to Sentry (all of them) (for profiling metrics)
// This number is relative to tracesSampleRate - so all traces get profiled
profilesSampleRate: 1.0,
Expand Down
Loading