From d9e7596d5407772166dadaebdaca002ede259ac3 Mon Sep 17 00:00:00 2001
From: Zack Tanner <1939140+ztanner@users.noreply.github.com>
Date: Fri, 19 Apr 2024 14:42:15 -0700
Subject: [PATCH] fix interception route rewrite regex not supporting special
characters
---
.../generate-interception-routes-rewrites.ts | 9 +++--
.../@intercept/(.)some-page/page.tsx | 8 ++++
.../[this-is-my-route]/@intercept/default.tsx | 3 ++
.../[this-is-my-route]/layout.tsx | 14 +++++++
.../[this-is-my-route]/page.tsx | 9 +++++
.../[this-is-my-route]/some-page/page.tsx | 8 ++++
.../parallel-routes-and-interception.test.ts | 37 +++++++++++++++++++
7 files changed, 85 insertions(+), 3 deletions(-)
create mode 100644 test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/@intercept/(.)some-page/page.tsx
create mode 100644 test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/@intercept/default.tsx
create mode 100644 test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/layout.tsx
create mode 100644 test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/page.tsx
create mode 100644 test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/some-page/page.tsx
diff --git a/packages/next/src/lib/generate-interception-routes-rewrites.ts b/packages/next/src/lib/generate-interception-routes-rewrites.ts
index 9247ae66566e2..0eb34537cb5e9 100644
--- a/packages/next/src/lib/generate-interception-routes-rewrites.ts
+++ b/packages/next/src/lib/generate-interception-routes-rewrites.ts
@@ -10,11 +10,14 @@ import type { Rewrite } from './load-custom-routes'
// a function that converts normalised paths (e.g. /foo/[bar]/[baz]) to the format expected by pathToRegexp (e.g. /foo/:bar/:baz)
function toPathToRegexpPath(path: string): string {
return path.replace(/\[\[?([^\]]+)\]\]?/g, (_, capture) => {
+ // path-to-regexp only supports word characters, so we replace any non-word characters with underscores
+ const paramName = capture.replace(/\W+/g, '_')
+
// handle catch-all segments (e.g. /foo/bar/[...baz] or /foo/bar/[[...baz]])
- if (capture.startsWith('...')) {
- return `:${capture.slice(3)}*`
+ if (paramName.startsWith('...')) {
+ return `:${paramName.slice(3)}*`
}
- return ':' + capture
+ return ':' + paramName
})
}
diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/@intercept/(.)some-page/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/@intercept/(.)some-page/page.tsx
new file mode 100644
index 0000000000000..134e0f7f9de15
--- /dev/null
+++ b/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/@intercept/(.)some-page/page.tsx
@@ -0,0 +1,8 @@
+export default function Page({ params }) {
+ return (
+
+ Hello from [this-is-my-route]/@intercept/some-page. Param:{' '}
+ {params['this-is-my-route']}
+
+ )
+}
diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/@intercept/default.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/@intercept/default.tsx
new file mode 100644
index 0000000000000..c17431379f962
--- /dev/null
+++ b/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/@intercept/default.tsx
@@ -0,0 +1,3 @@
+export default function Page() {
+ return null
+}
diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/layout.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/layout.tsx
new file mode 100644
index 0000000000000..bbb1f0cc14cc9
--- /dev/null
+++ b/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/layout.tsx
@@ -0,0 +1,14 @@
+export default function Layout({
+ children,
+ intercept,
+}: {
+ children: React.ReactNode
+ intercept: React.ReactNode
+}) {
+ return (
+
+
{children}
+
{intercept}
+
+ )
+}
diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/page.tsx
new file mode 100644
index 0000000000000..e8127244c2b66
--- /dev/null
+++ b/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/page.tsx
@@ -0,0 +1,9 @@
+import Link from 'next/link'
+
+export default function Page() {
+ return (
+
+ Trigger Interception
+
+ )
+}
diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/some-page/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/some-page/page.tsx
new file mode 100644
index 0000000000000..41ec03cf05bb5
--- /dev/null
+++ b/test/e2e/app-dir/parallel-routes-and-interception/app/interception-route-special-params/[this-is-my-route]/some-page/page.tsx
@@ -0,0 +1,8 @@
+export default function Page({ params }) {
+ return (
+
+ Hello from [this-is-my-route]/some-page. Param:{' '}
+ {params['this-is-my-route']}
+
+ )
+}
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 74b4e3d584b94..570bf2a75bd88 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
@@ -873,6 +873,43 @@ createNextDescribe(
await check(() => browser.waitForElementByCss('#main-slot').text(), '1')
})
+ it('should intercept on routes that contain hyphenated/special dynamic params', async () => {
+ const browser = await next.browser(
+ '/interception-route-special-params/some-random-param'
+ )
+
+ await browser
+ .elementByCss(
+ "[href='/interception-route-special-params/some-random-param/some-page']"
+ )
+ .click()
+
+ const interceptionText =
+ 'Hello from [this-is-my-route]/@intercept/some-page. Param: some-random-param'
+ const pageText =
+ 'Hello from [this-is-my-route]/some-page. Param: some-random-param'
+
+ await retry(async () => {
+ expect(await browser.elementByCss('body').text()).toContain(
+ interceptionText
+ )
+
+ expect(await browser.elementByCss('body').text()).not.toContain(
+ pageText
+ )
+ })
+
+ await browser.refresh()
+
+ await retry(async () => {
+ expect(await browser.elementByCss('body').text()).toContain(pageText)
+
+ expect(await browser.elementByCss('body').text()).not.toContain(
+ interceptionText
+ )
+ })
+ })
+
if (isNextStart) {
it('should not have /default paths in the prerender manifest', async () => {
const prerenderManifest = JSON.parse(