diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/server-action/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/server-action/page.tsx index 4137fafd9c3c..6784970d2aae 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/server-action/page.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/server-action/page.tsx @@ -1,5 +1,6 @@ import * as Sentry from '@sentry/nextjs'; import { headers } from 'next/headers'; +import { notFound } from 'next/navigation'; export default function ServerComponent() { async function myServerAction(formData: FormData) { @@ -14,11 +15,29 @@ export default function ServerComponent() { ); } + async function notFoundServerAction(formData: FormData) { + 'use server'; + return await Sentry.withServerActionInstrumentation( + 'notFoundServerAction', + { formData, headers: headers(), recordResponse: true }, + () => { + notFound(); + }, + ); + } + return ( - // @ts-ignore -
+ <> + {/* @ts-ignore */} + + {/* @ts-ignore */} + + > ); } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts index 3532c5c64746..6c99733381b6 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts @@ -140,6 +140,22 @@ test('Should send a transaction for instrumented server actions', async ({ page expect(Object.keys((await serverComponentTransactionPromise).request?.headers || {}).length).toBeGreaterThan(0); }); +test('Should set not_found status for server actions calling notFound()', async ({ page }) => { + const nextjsVersion = packageJson.dependencies.next; + const nextjsMajor = Number(nextjsVersion.split('.')[0]); + test.skip(!isNaN(nextjsMajor) && nextjsMajor < 14, 'only applies to nextjs apps >= version 14'); + + const serverComponentTransactionPromise = waitForTransaction('nextjs-13-app-dir', async transactionEvent => { + return transactionEvent?.transaction === 'serverAction/notFoundServerAction'; + }); + + await page.goto('/server-action'); + await page.getByText('Run NotFound Action').click(); + + expect(await serverComponentTransactionPromise).toBeDefined(); + expect(await (await serverComponentTransactionPromise).contexts?.trace?.status).toBe('not_found'); +}); + test('Will not include spans in pageload transaction with faulty timestamps for slow loading pages', async ({ page, }) => { diff --git a/packages/nextjs/src/common/withServerActionInstrumentation.ts b/packages/nextjs/src/common/withServerActionInstrumentation.ts index 0d0e6968a3b1..2fe1fd714b96 100644 --- a/packages/nextjs/src/common/withServerActionInstrumentation.ts +++ b/packages/nextjs/src/common/withServerActionInstrumentation.ts @@ -10,6 +10,7 @@ import { import { logger, tracingContextFromHeaders } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; +import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; import { platformSupportsStreaming } from './utils/platformSupportsStreaming'; import { flushQueue } from './utils/responseEnd'; @@ -101,7 +102,19 @@ async function withServerActionInstrumentationImplementation { const result = await handleCallbackErrors(callback, error => { - captureException(error, { mechanism: { handled: false } }); + if (isNotFoundNavigationError(error)) { + // We don't want to report "not-found"s + span?.setStatus('not_found'); + } else if (isRedirectNavigationError(error)) { + // Don't do anything for redirects + } else { + span?.setStatus('internal_error'); + captureException(error, { + mechanism: { + handled: false, + }, + }); + } }); if (options.recordResponse !== undefined ? options.recordResponse : sendDefaultPii) {