From 1268f99e9e404cd81a684a6865eac254656619f7 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 5 Jul 2024 16:22:54 +0200 Subject: [PATCH] Dedupe double logged server errors (#67464) ### What Only call error log in SSR renderer, instead of calling it in all RSC and SSR renderers. Fixes #67352 Close NEXT-3579 ### Why The RSC rendered generated result will be rendered by SSR renderer again, at there we can get the actual error. So when there's a RSC error happening, only logging once in SSR rendering layer is enough. --- .../app-render/create-error-handler.tsx | 8 ++++- .../app/client/edge/page.tsx | 7 ++++ .../dedupe-rsc-error-log/app/client/page.tsx | 7 ++++ .../dedupe-rsc-error-log/app/component.tsx | 8 +++++ .../dedupe-rsc-error-log/app/layout.tsx | 11 ++++++ .../app/server/edge/page.tsx | 5 +++ .../dedupe-rsc-error-log/app/server/page.tsx | 5 +++ .../dedupe-rsc-error-log.test.ts | 36 +++++++++++++++++++ 8 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 test/e2e/app-dir/dedupe-rsc-error-log/app/client/edge/page.tsx create mode 100644 test/e2e/app-dir/dedupe-rsc-error-log/app/client/page.tsx create mode 100644 test/e2e/app-dir/dedupe-rsc-error-log/app/component.tsx create mode 100644 test/e2e/app-dir/dedupe-rsc-error-log/app/layout.tsx create mode 100644 test/e2e/app-dir/dedupe-rsc-error-log/app/server/edge/page.tsx create mode 100644 test/e2e/app-dir/dedupe-rsc-error-log/app/server/page.tsx create mode 100644 test/e2e/app-dir/dedupe-rsc-error-log/dedupe-rsc-error-log.test.ts diff --git a/packages/next/src/server/app-render/create-error-handler.tsx b/packages/next/src/server/app-render/create-error-handler.tsx index b67fb304b4..008e152990 100644 --- a/packages/next/src/server/app-render/create-error-handler.tsx +++ b/packages/next/src/server/app-render/create-error-handler.tsx @@ -107,7 +107,13 @@ export function createErrorHandler({ }) } - if (!silenceLogger) { + if ( + (!silenceLogger && + // Only log the error from SSR rendering errors and flight data render errors, + // as RSC renderer error will still be pipped into SSR renderer as well. + source === 'html') || + source === 'flightData' + ) { if (errorLogger) { errorLogger(err).catch(() => {}) } else { diff --git a/test/e2e/app-dir/dedupe-rsc-error-log/app/client/edge/page.tsx b/test/e2e/app-dir/dedupe-rsc-error-log/app/client/edge/page.tsx new file mode 100644 index 0000000000..9f945bef43 --- /dev/null +++ b/test/e2e/app-dir/dedupe-rsc-error-log/app/client/edge/page.tsx @@ -0,0 +1,7 @@ +'use client' + +import { ErrorComponent } from '../../component' + +export default () => + +export const runtime = 'edge' diff --git a/test/e2e/app-dir/dedupe-rsc-error-log/app/client/page.tsx b/test/e2e/app-dir/dedupe-rsc-error-log/app/client/page.tsx new file mode 100644 index 0000000000..a25f860e19 --- /dev/null +++ b/test/e2e/app-dir/dedupe-rsc-error-log/app/client/page.tsx @@ -0,0 +1,7 @@ +'use client' + +import { ErrorComponent } from '../component' + +export default () => + +export const dynamic = 'force-dynamic' diff --git a/test/e2e/app-dir/dedupe-rsc-error-log/app/component.tsx b/test/e2e/app-dir/dedupe-rsc-error-log/app/component.tsx new file mode 100644 index 0000000000..a5e6d3411a --- /dev/null +++ b/test/e2e/app-dir/dedupe-rsc-error-log/app/component.tsx @@ -0,0 +1,8 @@ +async function getData(name: string) { + throw new Error('Custom error:' + name) +} + +export async function ErrorComponent({ name }: { name: string }) { + await getData(name) + return null +} diff --git a/test/e2e/app-dir/dedupe-rsc-error-log/app/layout.tsx b/test/e2e/app-dir/dedupe-rsc-error-log/app/layout.tsx new file mode 100644 index 0000000000..6a37cb1c70 --- /dev/null +++ b/test/e2e/app-dir/dedupe-rsc-error-log/app/layout.tsx @@ -0,0 +1,11 @@ +import { ReactNode } from 'react' + +export default function Root({ children }: { children: ReactNode }) { + return ( + + {children} + + ) +} + +export const dynamic = 'force-dynamic' diff --git a/test/e2e/app-dir/dedupe-rsc-error-log/app/server/edge/page.tsx b/test/e2e/app-dir/dedupe-rsc-error-log/app/server/edge/page.tsx new file mode 100644 index 0000000000..956cdaa660 --- /dev/null +++ b/test/e2e/app-dir/dedupe-rsc-error-log/app/server/edge/page.tsx @@ -0,0 +1,5 @@ +import { ErrorComponent } from '../../component' + +export default () => + +export const runtime = 'edge' diff --git a/test/e2e/app-dir/dedupe-rsc-error-log/app/server/page.tsx b/test/e2e/app-dir/dedupe-rsc-error-log/app/server/page.tsx new file mode 100644 index 0000000000..4555cd08c6 --- /dev/null +++ b/test/e2e/app-dir/dedupe-rsc-error-log/app/server/page.tsx @@ -0,0 +1,5 @@ +import { ErrorComponent } from '../component' + +export default () => + +export const dynamic = 'force-dynamic' diff --git a/test/e2e/app-dir/dedupe-rsc-error-log/dedupe-rsc-error-log.test.ts b/test/e2e/app-dir/dedupe-rsc-error-log/dedupe-rsc-error-log.test.ts new file mode 100644 index 0000000000..026aa8e48a --- /dev/null +++ b/test/e2e/app-dir/dedupe-rsc-error-log/dedupe-rsc-error-log.test.ts @@ -0,0 +1,36 @@ +import { nextTestSetup } from 'e2e-utils' +import { retry } from 'next-test-utils' + +async function expectContainOnce(next: any, search: string) { + // Ensure the search string is found once + await retry(() => { + const parts = next.cliOutput.split(search) + expect(parts.length).toBe(2) + }) +} + +describe('dedupe-rsc-error-log', () => { + const { next } = nextTestSetup({ + files: __dirname, + }) + + it('should only log RSC error once for nodejs runtime', async () => { + await next.fetch('/server') + await expectContainOnce(next, 'Custom error:server-node') + }) + + it('should only log RSC error once for edge runtime', async () => { + await next.fetch('/server/edge') + await expectContainOnce(next, 'Custom error:server-edge') + }) + + it('should only log SSR error once for nodejs runtime', async () => { + await next.fetch('/client') + await expectContainOnce(next, 'Custom error:client-node') + }) + + it('should only log SSR error once for edge runtime', async () => { + await next.fetch('/client/edge') + await expectContainOnce(next, 'Custom error:client-edge') + }) +})