Skip to content

Commit 0643828

Browse files
authored
fix(nextjs): Don't report NEXT_NOT_FOUND and NEXT_REDIRECT errors (#7642)
1 parent ccd5f27 commit 0643828

File tree

7 files changed

+84
-7
lines changed

7 files changed

+84
-7
lines changed

packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ export default function Layout({ children }: { children: React.ReactNode }) {
2929
<li>
3030
<Link href="/server-component/parameter/foo/bar/baz">/server-component/parameter/foo/bar/baz</Link>
3131
</li>
32+
<li>
33+
<Link href="/not-found">/not-found</Link>
34+
</li>
35+
<li>
36+
<Link href="/redirect">/redirect</Link>
37+
</li>
3238
</ul>
3339
<TransactionContextProvider>{children}</TransactionContextProvider>
3440
</div>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { ClientErrorDebugTools } from '../components/client-error-debug-tools';
2+
13
export default function NotFound() {
24
return (
35
<div style={{ border: '1px solid lightgrey', padding: '12px' }}>
46
<h2>Not found (/)</h2>;
7+
<ClientErrorDebugTools />
58
</div>
69
);
710
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { notFound } from 'next/navigation';
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export default function Page() {
6+
notFound();
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { redirect } from 'next/navigation';
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export default function Page() {
6+
redirect('/');
7+
}

packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,26 @@ if (process.env.TEST_ENV === 'production') {
8989
)
9090
.toBe(200);
9191
});
92+
93+
test('Should not set an error status on a server component transaction when it redirects', async ({ page }) => {
94+
const serverComponentTransactionPromise = waitForTransaction('nextjs-13-app-dir', async transactionEvent => {
95+
return transactionEvent?.transaction === 'Page Server Component (/server-component/redirect)';
96+
});
97+
98+
await page.goto('/server-component/redirect');
99+
100+
expect((await serverComponentTransactionPromise).contexts?.trace?.status).not.toBe('internal_error');
101+
});
102+
103+
test('Should set a "not_found" status on a server component transaction when notFound() is called', async ({
104+
page,
105+
}) => {
106+
const serverComponentTransactionPromise = waitForTransaction('nextjs-13-app-dir', async transactionEvent => {
107+
return transactionEvent?.transaction === 'Page Server Component (/server-component/not-found)';
108+
});
109+
110+
await page.goto('/server-component/not-found');
111+
112+
expect((await serverComponentTransactionPromise).contexts?.trace?.status).toBe('not_found');
113+
});
92114
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { isError } from '@sentry/utils';
2+
3+
/**
4+
* Determines whether input is a Next.js not-found error.
5+
* https://beta.nextjs.org/docs/api-reference/notfound#notfound
6+
*/
7+
export function isNotFoundNavigationError(subject: unknown): boolean {
8+
return isError(subject) && (subject as Error & { digest?: unknown }).digest === 'NEXT_NOT_FOUND';
9+
}
10+
11+
/**
12+
* Determines whether input is a Next.js redirect error.
13+
* https://beta.nextjs.org/docs/api-reference/redirect#redirect
14+
*/
15+
export function isRedirectNavigationError(subject: unknown): boolean {
16+
return (
17+
isError(subject) &&
18+
typeof (subject as Error & { digest?: unknown }).digest === 'string' &&
19+
(subject as Error & { digest: string }).digest.startsWith('NEXT_REDIRECT;') // a redirect digest looks like "NEXT_REDIRECT;[redirect path]"
20+
);
21+
}

packages/nextjs/src/server/wrapServerComponentWithSentry.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { addTracingExtensions, captureException, getCurrentHub, startTransaction
22
import { baggageHeaderToDynamicSamplingContext, extractTraceparentData } from '@sentry/utils';
33
import * as domain from 'domain';
44

5+
import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/nextNavigationErrorUtils';
56
import type { ServerComponentContext } from '../common/types';
67

78
/**
@@ -45,12 +46,24 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
4546
currentScope.setSpan(transaction);
4647
}
4748

49+
const handleErrorCase = (e: unknown): void => {
50+
if (isNotFoundNavigationError(e)) {
51+
// We don't want to report "not-found"s
52+
transaction.setStatus('not_found');
53+
} else if (isRedirectNavigationError(e)) {
54+
// We don't want to report redirects
55+
} else {
56+
transaction.setStatus('internal_error');
57+
captureException(e);
58+
}
59+
60+
transaction.finish();
61+
};
62+
4863
try {
4964
maybePromiseResult = originalFunction.apply(thisArg, args);
5065
} catch (e) {
51-
transaction.setStatus('internal_error');
52-
captureException(e);
53-
transaction.finish();
66+
handleErrorCase(e);
5467
throw e;
5568
}
5669

@@ -60,10 +73,8 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
6073
() => {
6174
transaction.finish();
6275
},
63-
(e: Error) => {
64-
transaction.setStatus('internal_error');
65-
captureException(e);
66-
transaction.finish();
76+
e => {
77+
handleErrorCase(e);
6778
},
6879
);
6980

0 commit comments

Comments
 (0)