diff --git a/packages/next/src/build/normalize-catchall-routes.ts b/packages/next/src/build/normalize-catchall-routes.ts index d933fd9b0a084..f89756ca187c4 100644 --- a/packages/next/src/build/normalize-catchall-routes.ts +++ b/packages/next/src/build/normalize-catchall-routes.ts @@ -12,7 +12,13 @@ export function normalizeCatchAllRoutes( normalizer = new AppPathnameNormalizer() ) { const catchAllRoutes = [ - ...new Set(Object.values(appPaths).flat().filter(isCatchAllRoute)), + ...new Set( + Object.values(appPaths) + .flat() + .filter(isCatchAllRoute) + // Sorting is important because we want to match the most specific path. + .sort((a, b) => b.split('/').length - a.split('/').length) + ), ] for (const appPath of Object.keys(appPaths)) { @@ -20,7 +26,7 @@ export function normalizeCatchAllRoutes( const normalizedCatchAllRoute = normalizer.normalize(catchAllRoute) const normalizedCatchAllRouteBasePath = normalizedCatchAllRoute.slice( 0, - normalizedCatchAllRoute.indexOf('[') + normalizedCatchAllRoute.search(catchAllRouteRegex) ) if ( @@ -48,6 +54,8 @@ function hasMatchedSlots(path1: string, path2: string): boolean { return true } +const catchAllRouteRegex = /\[?\[\.\.\./ + function isCatchAllRoute(pathname: string): boolean { return pathname.includes('[...') || pathname.includes('[[...') } diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/[...catchAll]/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/[...catchAll]/page.tsx new file mode 100644 index 0000000000000..c53a5bb486dbe --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/[...catchAll]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return 'slot catchall' +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/default.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/default.tsx new file mode 100644 index 0000000000000..129f875a30b3a --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/default.tsx @@ -0,0 +1,7 @@ +export default function Default() { + return ( +
+
Default
+
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/foo/[...catchAll]/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/foo/[...catchAll]/page.tsx new file mode 100644 index 0000000000000..156c8afb98132 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/foo/[...catchAll]/page.tsx @@ -0,0 +1,3 @@ +export default function Foo() { + return 'foo id catchAll' +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/foo/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/foo/page.tsx new file mode 100644 index 0000000000000..cfa4f3df3f08e --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/@slot/foo/page.tsx @@ -0,0 +1,3 @@ +export default function Foo() { + return 'foo slot' +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/[...catchAll]/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/[...catchAll]/page.tsx new file mode 100644 index 0000000000000..a305e6e15809b --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/[...catchAll]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return 'main catchall' +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/bar/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/bar/page.tsx new file mode 100644 index 0000000000000..6b789580e9532 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/bar/page.tsx @@ -0,0 +1,3 @@ +export default function Bar() { + return 'bar' +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/foo/[id]/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/foo/[id]/page.tsx new file mode 100644 index 0000000000000..4a40876d17775 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/foo/[id]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return 'foo id' +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/foo/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/foo/page.tsx new file mode 100644 index 0000000000000..fa0f41c9d1702 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/foo/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return 'foo' +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/layout.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/layout.tsx new file mode 100644 index 0000000000000..a98c912394ca7 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/layout.tsx @@ -0,0 +1,24 @@ +import Link from 'next/link' + +export default function Layout({ children, slot }) { + return ( +
+
Main content
+
{children}
+
+ Slot content: +
{slot}
+
+ +
+ foo +
+
+ catchall bar +
+
+ catchall foo id +
+
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/page.tsx new file mode 100644 index 0000000000000..cf1c703d31dcb --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested-catchall/page.tsx @@ -0,0 +1,7 @@ +export default function Page() { + return ( +
+
Page
+
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts b/test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts index 7cda69a91c47b..12d66ab205067 100644 --- a/test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts +++ b/test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts @@ -339,6 +339,37 @@ createNextDescribe( ) }) + it('Should match the catch-all routes of the more specific path, If there is more than one catch-all route', async () => { + const browser = await next.browser('/parallel-nested-catchall') + + await browser + .elementByCss('[href="/parallel-nested-catchall/foo"]') + .click() + await check(() => browser.waitForElementByCss('#main').text(), 'foo') + await check( + () => browser.waitForElementByCss('#slot-content').text(), + 'foo slot' + ) + + await browser + .elementByCss('[href="/parallel-nested-catchall/bar"]') + .click() + await check(() => browser.waitForElementByCss('#main').text(), 'bar') + await check( + () => browser.waitForElementByCss('#slot-content').text(), + 'slot catchall' + ) + + await browser + .elementByCss('[href="/parallel-nested-catchall/foo/123"]') + .click() + await check(() => browser.waitForElementByCss('#main').text(), 'foo id') + await check( + () => browser.waitForElementByCss('#slot-content').text(), + 'foo id catchAll' + ) + }) + it('should navigate with a link with prefetch=false', async () => { const browser = await next.browser('/parallel-prefetch-false')