From dab9038e1876ced92f18fb0232f879e0e9e6f740 Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Fri, 12 Jan 2024 06:59:48 -0800 Subject: [PATCH] propagate notFound errors past a segment's error boundary --- .../src/client/components/error-boundary.tsx | 7 +++++++ .../app-dir/not-found-default/index.test.ts | 14 -------------- .../basic/app/error-boundary/error.js | 4 ++++ .../error-boundary/nested/[dynamic]/page.js | 10 ++++++++++ .../error-boundary/nested/nested-2/error.js | 4 ++++ .../app/error-boundary/nested/nested-2/page.js | 18 ++++++++++++++++++ .../app/error-boundary/nested/not-found.js | 3 +++ .../not-found/basic/app/error-boundary/page.js | 18 ++++++++++++++++++ test/e2e/app-dir/not-found/basic/index.test.ts | 17 +++++++++++++++++ 9 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 test/e2e/app-dir/not-found/basic/app/error-boundary/error.js create mode 100644 test/e2e/app-dir/not-found/basic/app/error-boundary/nested/[dynamic]/page.js create mode 100644 test/e2e/app-dir/not-found/basic/app/error-boundary/nested/nested-2/error.js create mode 100644 test/e2e/app-dir/not-found/basic/app/error-boundary/nested/nested-2/page.js create mode 100644 test/e2e/app-dir/not-found/basic/app/error-boundary/nested/not-found.js create mode 100644 test/e2e/app-dir/not-found/basic/app/error-boundary/page.js diff --git a/packages/next/src/client/components/error-boundary.tsx b/packages/next/src/client/components/error-boundary.tsx index b6444a57d2a3f..13d87fb40c18e 100644 --- a/packages/next/src/client/components/error-boundary.tsx +++ b/packages/next/src/client/components/error-boundary.tsx @@ -2,6 +2,7 @@ import React from 'react' import { usePathname } from './navigation' +import { isNextRouterError } from './is-next-router-error' const styles = { error: { @@ -73,6 +74,12 @@ export class ErrorBoundaryHandler extends React.Component< } static getDerivedStateFromError(error: Error) { + if (isNextRouterError(error)) { + // Re-throw if an expected internal Next.js router error occurs + // this means it should be handled by a different boundary (such as a NotFound boundary in a parent segment) + throw error + } + return { error } } diff --git a/test/e2e/app-dir/not-found-default/index.test.ts b/test/e2e/app-dir/not-found-default/index.test.ts index 06037098b3751..fb882e9e5a22b 100644 --- a/test/e2e/app-dir/not-found-default/index.test.ts +++ b/test/e2e/app-dir/not-found-default/index.test.ts @@ -21,13 +21,6 @@ createNextDescribe( ) return 'success' }, /success/) - } else { - expect(await browser.elementByCss('h2').text()).toBe( - 'Application error: a server-side exception has occurred (see the server logs for more information).' - ) - expect(await browser.elementByCss('p').text()).toBe( - 'Digest: NEXT_NOT_FOUND' - ) } }) @@ -54,13 +47,6 @@ createNextDescribe( expect(await getRedboxDescription(browser)).toBe( 'Error: notFound() is not allowed to use in root layout' ) - } else { - expect(await browser.elementByCss('h2').text()).toBe( - 'Application error: a server-side exception has occurred (see the server logs for more information).' - ) - expect(await browser.elementByCss('p').text()).toBe( - 'Digest: NEXT_NOT_FOUND' - ) } }) diff --git a/test/e2e/app-dir/not-found/basic/app/error-boundary/error.js b/test/e2e/app-dir/not-found/basic/app/error-boundary/error.js new file mode 100644 index 0000000000000..35cc32fa29604 --- /dev/null +++ b/test/e2e/app-dir/not-found/basic/app/error-boundary/error.js @@ -0,0 +1,4 @@ +'use client' +export default function Error() { + return
There was an error
+} diff --git a/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/[dynamic]/page.js b/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/[dynamic]/page.js new file mode 100644 index 0000000000000..623218d0e366b --- /dev/null +++ b/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/[dynamic]/page.js @@ -0,0 +1,10 @@ +import { notFound } from 'next/navigation' +import React from 'react' + +export default function Page({ params }) { + if (params.dynamic === 'trigger-not-found') { + notFound() + } + + return
Hello World
+} diff --git a/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/nested-2/error.js b/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/nested-2/error.js new file mode 100644 index 0000000000000..35cc32fa29604 --- /dev/null +++ b/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/nested-2/error.js @@ -0,0 +1,4 @@ +'use client' +export default function Error() { + return
There was an error
+} diff --git a/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/nested-2/page.js b/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/nested-2/page.js new file mode 100644 index 0000000000000..926b94c6512b9 --- /dev/null +++ b/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/nested-2/page.js @@ -0,0 +1,18 @@ +'use client' +import { notFound } from 'next/navigation' +import React from 'react' +export default function Page() { + const [shouldError, setShouldError] = React.useState(false) + if (shouldError) { + notFound() + } + return ( + + ) +} diff --git a/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/not-found.js b/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/not-found.js new file mode 100644 index 0000000000000..8eb7f0bd63b60 --- /dev/null +++ b/test/e2e/app-dir/not-found/basic/app/error-boundary/nested/not-found.js @@ -0,0 +1,3 @@ +export default function Page() { + return

Not Found (error-boundary/nested)

+} diff --git a/test/e2e/app-dir/not-found/basic/app/error-boundary/page.js b/test/e2e/app-dir/not-found/basic/app/error-boundary/page.js new file mode 100644 index 0000000000000..926b94c6512b9 --- /dev/null +++ b/test/e2e/app-dir/not-found/basic/app/error-boundary/page.js @@ -0,0 +1,18 @@ +'use client' +import { notFound } from 'next/navigation' +import React from 'react' +export default function Page() { + const [shouldError, setShouldError] = React.useState(false) + if (shouldError) { + notFound() + } + return ( + + ) +} diff --git a/test/e2e/app-dir/not-found/basic/index.test.ts b/test/e2e/app-dir/not-found/basic/index.test.ts index 3ede15a1cea9c..bbef534c0aa57 100644 --- a/test/e2e/app-dir/not-found/basic/index.test.ts +++ b/test/e2e/app-dir/not-found/basic/index.test.ts @@ -8,6 +8,23 @@ createNextDescribe( skipDeployment: true, }, ({ next, isNextDev, isNextStart }) => { + it("should propagate notFound errors past a segment's error boundary", async () => { + let browser = await next.browser('/error-boundary') + await browser.elementByCss('button').click() + expect(await browser.elementByCss('h1').text()).toBe('Root Not Found') + + browser = await next.browser('/error-boundary/nested/nested-2') + await browser.elementByCss('button').click() + expect(await browser.elementByCss('h1').text()).toBe( + 'Not Found (error-boundary/nested)' + ) + + browser = await next.browser('/error-boundary/nested/trigger-not-found') + expect(await browser.elementByCss('h1').text()).toBe( + 'Not Found (error-boundary/nested)' + ) + }) + if (isNextStart) { it('should include not found client reference manifest in the file trace', async () => { const fileTrace = JSON.parse(