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 (
+
+ )
+}
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 (
+
+ )
+}
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')