Skip to content

Commit 96aa5eb

Browse files
authored
Merge pull request #32 from mrepol742/master
feat(sentry): Add Sentry integration and example
2 parents 5140221 + 4684acd commit 96aa5eb

13 files changed

Lines changed: 3205 additions & 184 deletions

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ NEXT_PUBLIC_SUPABASE_URL=
55
NEXT_PUBLIC_SUPABASE_ANON_KEY=
66

77
NEXT_PUBLIC_HCAPTCHA_SITE_KEY=
8+
9+
SENTRY_AUTH_TOKEN=

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,6 @@ supabase/
4949

5050
yarn.lock
5151
pnpm-lock.yaml
52+
53+
# Sentry Config File
54+
.env.sentry-build-plugin
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const dynamic = "force-dynamic";
2+
3+
class SentryExampleAPIError extends Error {
4+
constructor(message: string | undefined) {
5+
super(message);
6+
this.name = "SentryExampleAPIError";
7+
}
8+
}
9+
10+
// A faulty API route to test Sentry's error monitoring
11+
export function GET() {
12+
throw new SentryExampleAPIError(
13+
"This error is raised on the backend called by the example page.",
14+
);
15+
}

app/global-error.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use client";
2+
3+
import * as Sentry from "@sentry/nextjs";
4+
import NextError from "next/error";
5+
import { useEffect } from "react";
6+
7+
export default function GlobalError({
8+
error,
9+
}: {
10+
error: Error & { digest?: string };
11+
}) {
12+
useEffect(() => {
13+
Sentry.captureException(error);
14+
}, [error]);
15+
16+
return (
17+
<html lang="en">
18+
<body>
19+
{/* `NextError` is the default Next.js error page component. Its type
20+
definition requires a `statusCode` prop. However, since the App Router
21+
does not expose status codes for errors, we simply pass 0 to render a
22+
generic error message. */}
23+
<NextError statusCode={0} />
24+
</body>
25+
</html>
26+
);
27+
}

app/sentry-example-page/page.tsx

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
"use client";
2+
3+
import * as Sentry from "@sentry/nextjs";
4+
import Head from "next/head";
5+
import { useEffect, useState } from "react";
6+
7+
class SentryExampleFrontendError extends Error {
8+
constructor(message: string | undefined) {
9+
super(message);
10+
this.name = "SentryExampleFrontendError";
11+
}
12+
}
13+
14+
export default function Page() {
15+
const [hasSentError, setHasSentError] = useState(false);
16+
const [isConnected, setIsConnected] = useState(true);
17+
18+
useEffect(() => {
19+
async function checkConnectivity() {
20+
const result = await Sentry.diagnoseSdkConnectivity();
21+
setIsConnected(result !== "sentry-unreachable");
22+
}
23+
checkConnectivity();
24+
}, []);
25+
26+
return (
27+
<div>
28+
<Head>
29+
<title>sentry-example-page</title>
30+
<meta name="description" content="Test Sentry for your Next.js app!" />
31+
</Head>
32+
33+
<main>
34+
<div className="flex-spacer" />
35+
<svg
36+
height="40"
37+
width="40"
38+
fill="none"
39+
xmlns="http://www.w3.org/2000/svg"
40+
role="img"
41+
aria-label="Sentry logo"
42+
>
43+
<path
44+
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"
45+
fill="currentcolor"
46+
/>
47+
</svg>
48+
<h1>sentry-example-page</h1>
49+
50+
<p className="description">
51+
Click the button below, and view the sample error on the Sentry{" "}
52+
<a
53+
target="_blank"
54+
rel="noopener"
55+
href="https://melvin-jones-repol.sentry.io/issues/?project=4511078474711120"
56+
>
57+
Issues Page
58+
</a>
59+
. For more details about setting up Sentry,{" "}
60+
<a
61+
target="_blank"
62+
rel="noopener"
63+
href="https://docs.sentry.io/platforms/javascript/guides/nextjs/"
64+
>
65+
read our docs
66+
</a>
67+
.
68+
</p>
69+
70+
<button
71+
type="button"
72+
onClick={async () => {
73+
await Sentry.startSpan(
74+
{
75+
name: "Example Frontend/Backend Span",
76+
op: "test",
77+
},
78+
async () => {
79+
const res = await fetch("/api/sentry-example-api");
80+
if (!res.ok) {
81+
setHasSentError(true);
82+
}
83+
},
84+
);
85+
throw new SentryExampleFrontendError(
86+
"This error is raised on the frontend of the example page.",
87+
);
88+
}}
89+
disabled={!isConnected}
90+
>
91+
<span>Throw Sample Error</span>
92+
</button>
93+
94+
{hasSentError ? (
95+
<p className="success">Error sent to Sentry.</p>
96+
) : !isConnected ? (
97+
<div className="connectivity-error">
98+
<p>
99+
It looks like network requests to Sentry are being blocked, which
100+
will prevent errors from being captured. Try disabling your
101+
ad-blocker to complete the test.
102+
</p>
103+
</div>
104+
) : (
105+
<div className="success_placeholder" />
106+
)}
107+
108+
<div className="flex-spacer" />
109+
</main>
110+
111+
<style>{`
112+
main {
113+
display: flex;
114+
min-height: 100vh;
115+
flex-direction: column;
116+
justify-content: center;
117+
align-items: center;
118+
gap: 16px;
119+
padding: 16px;
120+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
121+
}
122+
123+
h1 {
124+
padding: 0px 4px;
125+
border-radius: 4px;
126+
background-color: rgba(24, 20, 35, 0.03);
127+
font-family: monospace;
128+
font-size: 20px;
129+
line-height: 1.2;
130+
}
131+
132+
p {
133+
margin: 0;
134+
font-size: 20px;
135+
}
136+
137+
a {
138+
color: #6341F0;
139+
text-decoration: underline;
140+
cursor: pointer;
141+
142+
@media (prefers-color-scheme: dark) {
143+
color: #B3A1FF;
144+
}
145+
}
146+
147+
button {
148+
border-radius: 8px;
149+
color: white;
150+
cursor: pointer;
151+
background-color: #553DB8;
152+
border: none;
153+
padding: 0;
154+
margin-top: 4px;
155+
156+
& > span {
157+
display: inline-block;
158+
padding: 12px 16px;
159+
border-radius: inherit;
160+
font-size: 20px;
161+
font-weight: bold;
162+
line-height: 1;
163+
background-color: #7553FF;
164+
border: 1px solid #553DB8;
165+
transform: translateY(-4px);
166+
}
167+
168+
&:hover > span {
169+
transform: translateY(-8px);
170+
}
171+
172+
&:active > span {
173+
transform: translateY(0);
174+
}
175+
176+
&:disabled {
177+
cursor: not-allowed;
178+
opacity: 0.6;
179+
180+
& > span {
181+
transform: translateY(0);
182+
border: none
183+
}
184+
}
185+
}
186+
187+
.description {
188+
text-align: center;
189+
color: #6E6C75;
190+
max-width: 500px;
191+
line-height: 1.5;
192+
font-size: 20px;
193+
194+
@media (prefers-color-scheme: dark) {
195+
color: #A49FB5;
196+
}
197+
}
198+
199+
.flex-spacer {
200+
flex: 1;
201+
}
202+
203+
.success {
204+
padding: 12px 16px;
205+
border-radius: 8px;
206+
font-size: 20px;
207+
line-height: 1;
208+
background-color: #00F261;
209+
border: 1px solid #00BF4D;
210+
color: #181423;
211+
}
212+
213+
.success_placeholder {
214+
height: 46px;
215+
}
216+
217+
.connectivity-error {
218+
padding: 12px 16px;
219+
background-color: #E50045;
220+
border-radius: 8px;
221+
width: 500px;
222+
color: #FFFFFF;
223+
border: 1px solid #A80033;
224+
text-align: center;
225+
margin: 0;
226+
}
227+
228+
.connectivity-error a {
229+
color: #FFFFFF;
230+
text-decoration: underline;
231+
}
232+
`}</style>
233+
</div>
234+
);
235+
}

instrumentation-client.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This file configures the initialization of Sentry on the client.
2+
// The added config here will be used whenever a users loads a page in their browser.
3+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
4+
5+
import * as Sentry from "@sentry/nextjs";
6+
7+
Sentry.init({
8+
dsn: "https://d1a39ca51d4a6f8ce8f14535febe5d6b@o4508073369862144.ingest.de.sentry.io/4511078474711120",
9+
10+
// Enable sending user PII (Personally Identifiable Information)
11+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
12+
sendDefaultPii: true,
13+
});
14+
15+
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;

instrumentation.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as Sentry from "@sentry/nextjs";
2+
3+
export async function register() {
4+
if (process.env.NEXT_RUNTIME === "nodejs") {
5+
await import("./sentry.server.config");
6+
}
7+
8+
if (process.env.NEXT_RUNTIME === "edge") {
9+
await import("./sentry.edge.config");
10+
}
11+
}
12+
13+
export const onRequestError = Sentry.captureRequestError;

next.config.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withSentryConfig } from "@sentry/nextjs";
12
import type { NextConfig } from "next";
23

34
const nextConfig: NextConfig = {
@@ -14,4 +15,40 @@ const nextConfig: NextConfig = {
1415
},
1516
};
1617

17-
export default nextConfig;
18+
export default withSentryConfig(nextConfig, {
19+
// For all available options, see:
20+
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
21+
22+
org: "melvin-jones-repol",
23+
24+
project: "devpulse",
25+
26+
// Only print logs for uploading source maps in CI
27+
silent: !process.env.CI,
28+
29+
// For all available options, see:
30+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
31+
32+
// Upload a larger set of source maps for prettier stack traces (increases build time)
33+
widenClientFileUpload: true,
34+
35+
// Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
36+
// This can increase your server load as well as your hosting bill.
37+
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
38+
// side errors will fail.
39+
// tunnelRoute: "/monitoring",
40+
41+
webpack: {
42+
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
43+
// See the following for more information:
44+
// https://docs.sentry.io/product/crons/
45+
// https://vercel.com/docs/cron-jobs
46+
automaticVercelMonitors: true,
47+
48+
// Tree-shaking options for reducing bundle size
49+
treeshake: {
50+
// Automatically tree-shake Sentry logger statements to reduce bundle size
51+
removeDebugLogging: true,
52+
},
53+
},
54+
});

0 commit comments

Comments
 (0)