Skip to content

Commit c305bf6

Browse files
authored
Add default not found to loader tree of group routes root layer (#54228)
For group routes, unlike normal routes, the root layout is located in the "group"'s segment instead of root layer. To make it also able to leverage the default not found error component as root not found component, we need to determine if we're in the root group segment in the loader tree, and add the not-found boundary for it. If you compre the loader tree visually they will look like this: Normal route tree structure ``` ['', { children: [segment, {...}, {...}]}, { layout: ..., 'not-found': ... } ] ``` Group routes ``` ['' { children: ['(group)', {...}, { layout, 'not-found': ... }]} {} ] ``` Comparing to normal case, we go 1 layer down in the tree for group routes, then insert the not-found boundary there Fixes #52255 Closes NEXT-1532
1 parent b906fdf commit c305bf6

File tree

4 files changed

+40
-2
lines changed

4 files changed

+40
-2
lines changed

packages/next/src/build/webpack/loaders/next-app-loader.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ export type ComponentsType = {
7575
readonly defaultPage?: ModuleReference
7676
}
7777

78+
function isGroupSegment(segment: string) {
79+
return segment.startsWith('(') && segment.endsWith(')')
80+
}
81+
7882
async function createAppRouteCode({
7983
name,
8084
page,
@@ -225,6 +229,7 @@ async function createTreeCodeFromPath(
225229

226230
// Existing tree are the children of the current segment
227231
const props: Record<string, string> = {}
232+
// Root layer could be 1st layer of normal routes
228233
const isRootLayer = segments.length === 0
229234
const isRootLayoutOrRootPage = segments.length <= 1
230235

@@ -314,10 +319,14 @@ async function createTreeCodeFromPath(
314319
)
315320

316321
// Add default not found error as root not found if not present
317-
const hasNotFound = definedFilePaths.some(
322+
const hasRootNotFound = definedFilePaths.some(
318323
([type]) => type === 'not-found'
319324
)
320-
if (isRootLayer && !hasNotFound) {
325+
// If the first layer is a group route, we treat it as root layer
326+
const isFirstLayerGroupRoute =
327+
segments.length === 1 &&
328+
subSegmentPath.filter((seg) => isGroupSegment(seg)).length === 1
329+
if ((isRootLayer || isFirstLayerGroupRoute) && !hasRootNotFound) {
321330
definedFilePaths.push(['not-found', defaultNotFoundPath])
322331
}
323332

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { notFound } from 'next/navigation'
2+
3+
export default function Page({ params }) {
4+
if (params.id === '404') {
5+
notFound()
6+
}
7+
8+
return <p id="page">{`group-dynamic [id]`}</p>
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function RootLayout({ children }) {
2+
return (
3+
<html className="group-root-layout">
4+
<body>{children}</body>
5+
</html>
6+
)
7+
}

test/e2e/app-dir/not-found-default/index.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,18 @@ createNextDescribe(
7272
'This page could not be found.'
7373
)
7474
})
75+
76+
it('should render default not found for group routes if not found is not defined', async () => {
77+
const browser = await next.browser('/group-dynamic/123')
78+
expect(await browser.elementByCss('#page').text()).toBe(
79+
'group-dynamic [id]'
80+
)
81+
82+
await browser.loadPage(next.url + '/group-dynamic/404')
83+
expect(await browser.elementByCss('.next-error-h1').text()).toBe('404')
84+
expect(await browser.elementByCss('html').getAttribute('class')).toBe(
85+
'group-root-layout'
86+
)
87+
})
7588
}
7689
)

0 commit comments

Comments
 (0)