From dc13cf6762a079a08967ef33db0038b57d54bc19 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 +++++++ .../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 +++++++++++++++++ 8 files changed, 81 insertions(+) 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 b6444a57d2a3f0..13d87fb40c18e7 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/basic/app/error-boundary/error.js b/test/e2e/app-dir/not-found/basic/app/error-boundary/error.js new file mode 100644 index 00000000000000..35cc32fa296043 --- /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 00000000000000..623218d0e366bc --- /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 00000000000000..35cc32fa296043 --- /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 00000000000000..926b94c6512b9c --- /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 00000000000000..8eb7f0bd63b60f --- /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 00000000000000..926b94c6512b9c --- /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 3ede15a1cea9cd..bbef534c0aa57a 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(