Skip to content

Commit 0db01f8

Browse files
authored
fix(nextjs): Don't capture devmode server-action redirect errors (#15485)
Fixes #15476
1 parent c0d3c27 commit 0db01f8

File tree

3 files changed

+53
-5
lines changed

3 files changed

+53
-5
lines changed

dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/server-action/page.tsx

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as Sentry from '@sentry/nextjs';
22
import { headers } from 'next/headers';
3-
import { notFound } from 'next/navigation';
3+
import { notFound, redirect } from 'next/navigation';
44

55
export default function ServerComponent() {
66
async function myServerAction(formData: FormData) {
@@ -26,6 +26,17 @@ export default function ServerComponent() {
2626
);
2727
}
2828

29+
async function redirectServerAction(formData: FormData) {
30+
'use server';
31+
return await Sentry.withServerActionInstrumentation(
32+
'redirectServerAction',
33+
{ formData, headers: headers(), recordResponse: true },
34+
() => {
35+
redirect('/');
36+
},
37+
);
38+
}
39+
2940
return (
3041
<>
3142
{/* @ts-ignore */}
@@ -38,6 +49,11 @@ export default function ServerComponent() {
3849
<input type="text" defaultValue={'some-default-value'} name="some-text-value" />
3950
<button type="submit">Run NotFound Action</button>
4051
</form>
52+
{/* @ts-ignore */}
53+
<form action={redirectServerAction}>
54+
<input type="text" defaultValue={'some-default-value'} name="some-text-value" />
55+
<button type="submit">Run Redirect Action</button>
56+
</form>
4157
</>
4258
);
4359
}

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

+33-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForTransaction } from '@sentry-internal/test-utils';
2+
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
33

44
const packageJson = require('../package.json');
55

@@ -108,19 +108,49 @@ test('Should set not_found status for server actions calling notFound()', async
108108
const nextjsMajor = Number(nextjsVersion.split('.')[0]);
109109
test.skip(!isNaN(nextjsMajor) && nextjsMajor < 14, 'only applies to nextjs apps >= version 14');
110110

111-
const serverComponentTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => {
111+
const serverActionTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => {
112112
return transactionEvent?.transaction === 'serverAction/notFoundServerAction';
113113
});
114114

115115
await page.goto('/server-action');
116116
await page.getByText('Run NotFound Action').click();
117117

118-
const transactionEvent = await serverComponentTransactionPromise;
118+
const transactionEvent = await serverActionTransactionPromise;
119119

120120
expect(transactionEvent).toBeDefined();
121121
expect(transactionEvent.contexts?.trace?.status).toBe('not_found');
122122
});
123123

124+
test('Should not capture "NEXT_REDIRECT" control-flow errors for server actions calling redirect()', async ({
125+
page,
126+
}) => {
127+
const nextjsVersion = packageJson.dependencies.next;
128+
const nextjsMajor = Number(nextjsVersion.split('.')[0]);
129+
test.skip(!isNaN(nextjsMajor) && nextjsMajor < 14, 'only applies to nextjs apps >= version 14');
130+
131+
const serverActionTransactionPromise = waitForTransaction('nextjs-app-dir', transactionEvent => {
132+
return transactionEvent?.transaction === 'serverAction/redirectServerAction';
133+
});
134+
135+
let controlFlowErrorCaptured = false;
136+
waitForError('nextjs-app-dir', errorEvent => {
137+
if (errorEvent.exception?.values?.[0].value === 'NEXT_REDIRECT') {
138+
controlFlowErrorCaptured = true;
139+
}
140+
141+
return false;
142+
});
143+
144+
await page.goto('/server-action');
145+
await page.getByText('Run Redirect Action').click();
146+
147+
const serverActionTransactionEvent = await serverActionTransactionPromise;
148+
expect(serverActionTransactionEvent).toBeDefined();
149+
150+
// By the time the server action span finishes the error should already have been sent
151+
expect(controlFlowErrorCaptured).toBe(false);
152+
});
153+
124154
test('Will not include spans in pageload transaction with faulty timestamps for slow loading pages', async ({
125155
page,
126156
}) => {

packages/nextjs/src/client/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ export function init(options: BrowserOptions): Client | undefined {
5151
addEventProcessor(filterIncompleteNavigationTransactions);
5252

5353
const filterNextRedirectError: EventProcessor = (event, hint) =>
54-
isRedirectNavigationError(hint?.originalException) ? null : event;
54+
isRedirectNavigationError(hint?.originalException) || event.exception?.values?.[0]?.value === 'NEXT_REDIRECT'
55+
? null
56+
: event;
5557
filterNextRedirectError.id = 'NextRedirectErrorFilter';
5658
addEventProcessor(filterNextRedirectError);
5759

0 commit comments

Comments
 (0)