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.
This commit is contained in:
Jiachi Liu 2024-07-05 16:22:54 +02:00 committed by GitHub
parent bda92a1be5
commit 1268f99e9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 86 additions and 1 deletions

View file

@ -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 {

View file

@ -0,0 +1,7 @@
'use client'
import { ErrorComponent } from '../../component'
export default () => <ErrorComponent name="client-edge" />
export const runtime = 'edge'

View file

@ -0,0 +1,7 @@
'use client'
import { ErrorComponent } from '../component'
export default () => <ErrorComponent name="client-node" />
export const dynamic = 'force-dynamic'

View file

@ -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
}

View file

@ -0,0 +1,11 @@
import { ReactNode } from 'react'
export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
export const dynamic = 'force-dynamic'

View file

@ -0,0 +1,5 @@
import { ErrorComponent } from '../../component'
export default () => <ErrorComponent name="server-edge" />
export const runtime = 'edge'

View file

@ -0,0 +1,5 @@
import { ErrorComponent } from '../component'
export default () => <ErrorComponent name="server-node" />
export const dynamic = 'force-dynamic'

View file

@ -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')
})
})