90f95399dd
### What? When relying on a `default` route as a fallback, greedier catch-all segments in the application hierarchy would take precedence, causing unexpected errors/matching behavior. ### Why? When performing parallel route catch-all normalization, we push potential catch-all matches to paths without considering that a path might instead be matched by a `default` page. Because of this, the catch-all take precedence and the app will not try and load the default. For example, given this structure: ``` { "/": ["/page"], "/[[...catchAll]]": ["/[[...catchAll]]/page"], "/nested/[foo]/[bar]": ["/nested/[foo]/[bar]/@slot/page"], "/nested/[foo]/[bar]/[baz]": ["/nested/[foo]/[bar]/@slot/[baz]/page"], } ``` (Where there's a `/nested/[foo]/[bar]/default.tsx`) The route normalization logic would produce: ``` { "/": ["/page"], "/[[...catchAll]]": ["/[[...catchAll]]/page"], "/nested/[foo]/[bar]": [ "/nested/[foo]/[bar]/@slot/page", "/[[...catchAll]]/page", ], "/nested/[foo]/[bar]/[baz]": [ "/nested/[foo]/[bar]/@slot/[baz]/page", "/[[...catchAll]]/page", ], } ``` This means that when building the `LoaderTree`, it won't ever try to find the default for that segment. **This solution operates under the assumption that if you defined a `default` at a particular layout segment, you intend for that to render in place of a greedier catch-all.** (Let me know if this is an incorrect assumption) ### How? We can't safely normalize catch-all parallel routes without having context about where the `default` segments are, so this updates `appPaths` to be inclusive of default segments and then filters them when doing anything relating to build/export to maintain existing behavior. We use this information to check if an existing default exists at the same segment level that we'd push the catch-all to. If one exists, we don't push the catch-all. Otherwise we proceed as normal. Closes NEXT-1987
44 lines
1.7 KiB
TypeScript
44 lines
1.7 KiB
TypeScript
import { createNextDescribe } from 'e2e-utils'
|
|
|
|
createNextDescribe(
|
|
'parallel-routes-catchall-default',
|
|
{
|
|
files: __dirname,
|
|
},
|
|
({ next }) => {
|
|
it('should match default paths before catch-all', async () => {
|
|
let browser = await next.browser('/en/nested')
|
|
|
|
// we have a top-level catch-all but the /nested dir doesn't have a default/page until the /[foo]/[bar] segment
|
|
// so we expect the top-level catch-all to render
|
|
expect(await browser.elementById('children').text()).toBe(
|
|
'/[locale]/[[...catchAll]]/page.tsx'
|
|
)
|
|
|
|
browser = await next.browser('/en/nested/foo/bar')
|
|
|
|
// we're now at the /[foo]/[bar] segment, so we expect the matched page to be the default (since there's no page defined)
|
|
expect(await browser.elementById('nested-children').text()).toBe(
|
|
'/[locale]/nested/[foo]/[bar]/default.tsx'
|
|
)
|
|
|
|
// we expect the slot to match since there's a page defined at this segment
|
|
expect(await browser.elementById('slot').text()).toBe(
|
|
'/[locale]/nested/[foo]/[bar]/@slot/page.tsx'
|
|
)
|
|
|
|
browser = await next.browser('/en/nested/foo/bar/baz')
|
|
|
|
// the page slot should still be the one matched at the /[foo]/[bar] segment because it's the default and we
|
|
// didn't define a page at the /[foo]/[bar]/[baz] segment
|
|
expect(await browser.elementById('nested-children').text()).toBe(
|
|
'/[locale]/nested/[foo]/[bar]/default.tsx'
|
|
)
|
|
|
|
// however we do have a slot for the `[baz]` segment and so we expect that to no match
|
|
expect(await browser.elementById('slot').text()).toBe(
|
|
'/[locale]/nested/[foo]/[bar]/@slot/[baz]/page.tsx'
|
|
)
|
|
})
|
|
}
|
|
)
|