Skip to content

Commit 41951ba

Browse files
author
Luca Forstner
authored
fix(nextjs): Don't report React postpone errors (#12194)
1 parent a9cef35 commit 41951ba

File tree

4 files changed

+71
-2
lines changed

4 files changed

+71
-2
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
3+
export default async function Page({
4+
searchParams,
5+
}: {
6+
searchParams: { id?: string };
7+
}) {
8+
try {
9+
console.log(searchParams.id); // Accessing a field on searchParams will throw the PPR error
10+
} catch (e) {
11+
Sentry.captureException(e); // This error should not be reported
12+
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for any async event processors to run
13+
await Sentry.flush();
14+
throw e;
15+
}
16+
17+
return <div>This server component will throw a PPR error that we do not want to catch.</div>;
18+
}

dev-packages/e2e-tests/test-applications/nextjs-15/next.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
const { withSentryConfig } = require('@sentry/nextjs');
22

33
/** @type {import('next').NextConfig} */
4-
const nextConfig = {};
4+
const nextConfig = {
5+
experimental: {
6+
ppr: true,
7+
},
8+
};
59

610
module.exports = withSentryConfig(nextConfig, {
711
silent: true,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForError, waitForTransaction } from '@sentry-internal/event-proxy-server';
3+
4+
test('should not capture React-internal errors for PPR rendering', async ({ page }) => {
5+
const pageServerComponentTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
6+
return transactionEvent?.transaction === 'Page Server Component (/ppr-error/[param])';
7+
});
8+
9+
let errorEventReceived = false;
10+
waitForError('nextjs-15', async transactionEvent => {
11+
return transactionEvent?.transaction === 'Page Server Component (/ppr-error/[param])';
12+
}).then(() => {
13+
errorEventReceived = true;
14+
});
15+
16+
await page.goto(`/ppr-error/foobar?id=1`);
17+
18+
const pageServerComponentTransaction = await pageServerComponentTransactionPromise;
19+
expect(pageServerComponentTransaction).toBeDefined();
20+
21+
expect(errorEventReceived).toBe(false);
22+
});

packages/nextjs/src/server/index.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { addEventProcessor, applySdkMetadata, getClient } from '@sentry/core';
1+
import { addEventProcessor, applySdkMetadata, getClient, getGlobalScope } from '@sentry/core';
22
import { getDefaultIntegrations, init as nodeInit } from '@sentry/node';
33
import type { NodeOptions } from '@sentry/node';
44
import { GLOBAL_OBJ, logger } from '@sentry/utils';
@@ -181,6 +181,31 @@ export function init(options: NodeOptions): void {
181181
),
182182
);
183183

184+
getGlobalScope().addEventProcessor(
185+
Object.assign(
186+
((event, hint) => {
187+
if (event.type !== undefined) {
188+
return event;
189+
}
190+
191+
const originalException = hint.originalException;
192+
193+
const isReactControlFlowError =
194+
typeof originalException === 'object' &&
195+
originalException !== null &&
196+
'$$typeof' in originalException &&
197+
originalException.$$typeof === Symbol.for('react.postpone');
198+
199+
if (isReactControlFlowError) {
200+
return null;
201+
}
202+
203+
return event;
204+
}) satisfies EventProcessor,
205+
{ id: 'DropReactControlFlowErrors' },
206+
),
207+
);
208+
184209
if (process.env.NODE_ENV === 'development') {
185210
addEventProcessor(devErrorSymbolicationEventProcessor);
186211
}

0 commit comments

Comments
 (0)