Create hash digest for errors in app in production (#41559)

This commit is contained in:
Tim Neutkens 2022-10-19 19:56:55 +02:00 committed by GitHub
parent fda355daa2
commit 4ba1002175
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 73 additions and 13 deletions

View file

@ -42,6 +42,7 @@ import { DYNAMIC_ERROR_CODE } from '../client/components/hooks-server-context'
import { NOT_FOUND_ERROR_CODE } from '../client/components/not-found'
import { HeadManagerContext } from '../shared/lib/head-manager-context'
import { Writable } from 'stream'
import stringHash from 'next/dist/compiled/string-hash'
const INTERNAL_HEADERS_INSTANCE = Symbol('internal for headers readonly')
@ -176,21 +177,21 @@ function createErrorHandler(
_source: string,
capturedErrors: Error[]
) {
return (err: any) => {
return (err: any): string => {
if (
// TODO-APP: Handle redirect throw
err.digest !== DYNAMIC_ERROR_CODE &&
err.digest !== NOT_FOUND_ERROR_CODE &&
!err.digest?.startsWith(REDIRECT_ERROR_CODE)
err.digest === DYNAMIC_ERROR_CODE ||
err.digest === NOT_FOUND_ERROR_CODE ||
err.digest?.startsWith(REDIRECT_ERROR_CODE)
) {
// Used for debugging error source
// console.error(_source, err)
console.error(err)
capturedErrors.push(err)
return err.digest || err.message
return err.digest
}
return err.digest
// Used for debugging error source
// console.error(_source, err)
console.error(err)
capturedErrors.push(err)
// TODO-APP: look at using webcrypto instead. Requires a promise to be awaited.
return stringHash(err.message + err.stack + (err.digest || '')).toString()
}
}

View file

@ -0,0 +1,9 @@
export const config = {
revalidate: 0,
}
export default function Page() {
const err = new Error('this is a test')
err.digest = 'custom'
throw err
}

View file

@ -0,0 +1,13 @@
'use client'
export default function ErrorBoundary({ error, reset }) {
return (
<>
<p id="error-boundary-message">{error.message}</p>
<p id="error-boundary-digest">{error.digest}</p>
<button id="reset" onClick={() => reset()}>
Try again
</button>
</>
)
}

View file

@ -0,0 +1,7 @@
export const config = {
revalidate: 0,
}
export default function Page() {
throw new Error('this is a test')
}

View file

@ -1553,8 +1553,7 @@ describe('app dir', () => {
if (isDev) {
expect(await hasRedbox(browser)).toBe(true)
console.log('getRedboxHeader', await getRedboxHeader(browser))
// expect(await getRedboxHeader(browser)).toMatch(/An error occurred: this is a test/)
expect(await getRedboxHeader(browser)).toMatch(/this is a test/)
} else {
await browser
expect(
@ -1566,6 +1565,37 @@ describe('app dir', () => {
}
})
it('should trigger error component when an error happens during server components rendering', async () => {
const browser = await webdriver(next.url, '/error/server-component')
if (isDev) {
expect(
await browser
.waitForElementByCss('#error-boundary-message')
.elementByCss('#error-boundary-message')
.text()
).toBe('this is a test')
expect(
await browser.waitForElementByCss('#error-boundary-digest').text()
// Digest of the error message should be stable.
).not.toBe('')
// TODO-APP: ensure error overlay is shown for errors that happened before/during hydration
// expect(await hasRedbox(browser)).toBe(true)
// expect(await getRedboxHeader(browser)).toMatch(/this is a test/)
} else {
await browser
expect(
await browser.waitForElementByCss('#error-boundary-message').text()
).toBe(
'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.'
)
expect(
await browser.waitForElementByCss('#error-boundary-digest').text()
// Digest of the error message should be stable.
).not.toBe('')
}
})
it('should use default error boundary for prod and overlay for dev when no error component specified', async () => {
const browser = await webdriver(
next.url,