Skip to content

Commit 504a52a

Browse files
Reflex2468ztanner
andauthored
Fixed interception on a catch-all route (#72902)
## For Contributors ### Fixing a bug Fixes #72523 Related to b914ad8 Related to #51787 ## For Maintainers - Fixes catch-all segments in intercepted routes. ### What? The regex replacement, also replaces `...` with `_`. Which causes the check below, which handles catch-all segments, to always return false. ``` const paramName = capture.replace(/\W+/g, '_'); ``` ### Why? Currently when you have an intercepted route with a catch-all segment, it only matches the first segment. As soon as you navigate to `/multiple/segments` the intercept fails because the rewrite is not capturing this correctly. ### How? The changes introduced in b914ad8 added a `replace(/\W+/g, '_')` which causes the check below to always fail, since if it started with `...` that would at that point be replaced with `_`. ### Todo - [ ] write tests to verify the return value of `rewrites` has `:path*` instead of `:path` --------- Co-authored-by: Zack Tanner <[email protected]>
1 parent b6be532 commit 504a52a

File tree

8 files changed

+117
-2
lines changed

8 files changed

+117
-2
lines changed

packages/next/src/lib/generate-interception-routes-rewrites.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ function toPathToRegexpPath(path: string): string {
1313
const paramName = capture.replace(/\W+/g, '_')
1414

1515
// handle catch-all segments (e.g. /foo/bar/[...baz] or /foo/bar/[[...baz]])
16-
if (paramName.startsWith('...')) {
17-
return `:${paramName.slice(3)}*`
16+
if (capture.startsWith('...')) {
17+
return `:${capture.slice(3)}*`
1818
}
1919
return ':' + paramName
2020
})
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Link from 'next/link'
2+
import React from 'react'
3+
4+
async function Page({ params }: { params: Promise<{ catchAll: string[] }> }) {
5+
const { catchAll } = await params
6+
return (
7+
<div className="text-lg">
8+
Showcase Simple Page
9+
<Link href={`/templates/${catchAll.join('/')}`}>
10+
templates {catchAll.join(' -> ')}
11+
</Link>
12+
</div>
13+
)
14+
}
15+
16+
export default Page
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default function RootLayout({
2+
children,
3+
}: Readonly<{ children: React.ReactNode }>) {
4+
return (
5+
<html lang="en">
6+
<body>{children}</body>
7+
</html>
8+
)
9+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Link from 'next/link'
2+
3+
export default function HomePage() {
4+
return <Link href="/templates/multi/slug">Go to test page</Link>
5+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Link from 'next/link'
2+
import React from 'react'
3+
4+
async function Page() {
5+
return (
6+
<div id="intercepting-page">
7+
Showcase Intercepting Page
8+
<Link href="/templates/single">Single</Link>
9+
</div>
10+
)
11+
}
12+
13+
export default Page
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Link from 'next/link'
2+
import React from 'react'
3+
4+
async function Page({ params }: { params: Promise<{ catchAll: string[] }> }) {
5+
const { catchAll } = await params
6+
return (
7+
<div>
8+
Simple Page
9+
<Link href={`/showcase/${catchAll.join('/')}`}>
10+
To Same Path <span className="opacity-70">{catchAll.join(' -> ')}</span>
11+
</Link>
12+
<Link href="/showcase/single">To Single Path</Link>
13+
<Link href="/showcase/another/slug">multi-slug</Link>
14+
</div>
15+
)
16+
}
17+
18+
export default Page
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Link from 'next/link'
2+
import React from 'react'
3+
4+
function Page() {
5+
return (
6+
<div>
7+
Simple Page
8+
<Link href={'/showcase/new'}>showcase/new</Link>
9+
</div>
10+
)
11+
}
12+
13+
export default Page
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('interception-routes-multiple-catchall', () => {
4+
const { next } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
describe('multi-param catch-all', () => {
9+
it('should intercept when navigating to the same path with route interception', async () => {
10+
const browser = await next.browser('/templates/multi/slug')
11+
await browser.elementByCss("[href='/showcase/multi/slug']").click()
12+
await browser.waitForElementByCss('#intercepting-page')
13+
})
14+
15+
it('should intercept when navigating to a single param path', async () => {
16+
const browser = await next.browser('/templates/multi/slug')
17+
await browser.elementByCss("[href='/showcase/single']").click()
18+
await browser.waitForElementByCss('#intercepting-page')
19+
})
20+
21+
it('should intercept when navigating to a multi-param path', async () => {
22+
const browser = await next.browser('/templates/multi/slug')
23+
await browser.elementByCss("[href='/showcase/another/slug']").click()
24+
await browser.waitForElementByCss('#intercepting-page')
25+
})
26+
})
27+
28+
describe('single param catch-all', () => {
29+
it('should intercept when navigating to a multi-param path', async () => {
30+
const browser = await next.browser('/templates/single')
31+
await browser.elementByCss("[href='/showcase/another/slug']").click()
32+
await browser.waitForElementByCss('#intercepting-page')
33+
})
34+
35+
it('should intercept when navigating to a single param path', async () => {
36+
const browser = await next.browser('/templates/single')
37+
await browser.elementByCss("[href='/showcase/single']").click()
38+
await browser.waitForElementByCss('#intercepting-page')
39+
})
40+
})
41+
})

0 commit comments

Comments
 (0)