From 412992ad6ea298c54e35946cca7c9e1da7c01a06 Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Fri, 7 Jul 2023 00:41:21 -0700 Subject: [PATCH] fix: prevent infinite dev refresh on nested parallel routes (#52362) ### What? HMR causes infinite reloads for parallel routes when the corresponding page component is nested ### Why? In 4900fa21b078fd1ec1adc5d570fcfb560be8aeb6, code was added to remove `/@children` from the page path (if present) but in 59b36349eb86427ac7b679ac62fa6628c9fc4886, `normalizeParallelKey` removes the @ prefix from children, so this doesn't seem to be catching the scenario it was intended to prevent ### How? This updates the existing replace logic to consider `/children/page` rather than `/@children/page` -- it doesn't seem like `/@children` is a valid scenario given the `normalizeParallelKey` behavior Fixes #52342 and addresses the concerns in https://github.com/vercel/next.js/pull/52061#issuecomment-1619145129 --- .../src/server/dev/on-demand-entry-handler.ts | 6 +++--- .../home/@parallelB/nested/page.tsx | 8 ++++++++ .../app/parallel-nested/home/layout.tsx | 8 ++++++++ .../app/parallel-nested/layout.tsx | 10 ++++++++++ .../parallel-routes-and-interception.test.ts | 15 +++++++++++++++ 5 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/home/@parallelB/nested/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/home/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/layout.tsx diff --git a/packages/next/src/server/dev/on-demand-entry-handler.ts b/packages/next/src/server/dev/on-demand-entry-handler.ts index 184584398bcc6..efe9d228782a5 100644 --- a/packages/next/src/server/dev/on-demand-entry-handler.ts +++ b/packages/next/src/server/dev/on-demand-entry-handler.ts @@ -98,9 +98,9 @@ export function getEntryKey( pageBundleType: 'app' | 'pages' | 'root', page: string ) { - // TODO: handle the /@children slot better - // this is a quick hack to handle when children is provided as @children/page instead of /page - return `${compilerType}@${pageBundleType}@${page.replace(/\/@children/g, '')}` + // TODO: handle the /children slot better + // this is a quick hack to handle when children is provided as children/page instead of /page + return `${compilerType}@${pageBundleType}@${page.replace(/\/children/g, '')}` } function getPageBundleType(pageBundlePath: string) { diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/home/@parallelB/nested/page.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/home/@parallelB/nested/page.tsx new file mode 100644 index 0000000000000..32ea4239349fe --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/home/@parallelB/nested/page.tsx @@ -0,0 +1,8 @@ +export default function ParallelPage() { + return ( + <> +

Hello from nested parallel page!

+
{Date.now()}
+ + ) +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/home/layout.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/home/layout.tsx new file mode 100644 index 0000000000000..d087314f3ca85 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/home/layout.tsx @@ -0,0 +1,8 @@ +export default function Layout({ parallelB }: { parallelB: React.ReactNode }) { + return ( +
+

{`(parallelB)`}

+
{parallelB}
+
+ ) +} diff --git a/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/layout.tsx b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/layout.tsx new file mode 100644 index 0000000000000..13f93141f887d --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-and-interception/app/parallel-nested/layout.tsx @@ -0,0 +1,10 @@ +export default function Parallel({ children }) { + return ( +
+ parallel/layout: +
+ {children} +
+
+ ) +} 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 d2890a40558cd..a5b172135070b 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 @@ -354,6 +354,21 @@ createNextDescribe( return newTimestamp !== timestamp ? 'failure' : 'success' }, 'success') }) + + it('should support nested parallel routes', async () => { + const browser = await next.browser('parallel-nested/home/nested') + const timestamp = await browser.elementByCss('#timestamp').text() + + await new Promise((resolve) => { + setTimeout(resolve, 3000) + }) + + await check(async () => { + // an invalid response triggers a fast refresh, so if the timestamp doesn't update, this behaved correctly + const newTimestamp = await browser.elementByCss('#timestamp').text() + return newTimestamp !== timestamp ? 'failure' : 'success' + }, 'success') + }) } })