next.js: Intercepting parallel routes including dynamic route not working within the same directory

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: x64
      Version: Darwin Kernel Version 22.5.0: Thu Jun  8 22:22:22 PDT 2023; root:xnu-8796.121.3~7/RELEASE_X86_64
    Binaries:
      Node: 18.16.1
      npm: 9.8.0
      Yarn: 1.22.19
      pnpm: N/A
    Relevant Packages:
      next: 13.4.10-canary.3
      eslint-config-next: 13.4.9
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.1.6
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

App Router

Link to the code that reproduces this issue or a replay of the bug

https://github.com/zoogeny/next-js-interceptor-parallel-dynamic

To Reproduce

Checkout, install the project and run the dev server

git checkout https://github.com/zoogeny/next-js-interceptor-parallel-dynamic.git   
cd next-js-interceptor-parallel-dynamic
npm install
npm run dev

Load a page in the blog (e.g. http://localhost:3000/blog/first-post) and you will see a basic interface:

params.slug: first-post
Same dir
Nested dir

If you click the Nested dir link you will correctly get a parallel slot rendered below the links with a red border. If you click within the border the slot will be removed.

If you click the Same dir link you will navigate the entire page. This is not expected - it should be intercepted and render inside the parallel slot.

Describe the Bug

It appears that interception of routes within the same directory does not work. e.g. I have a directory structure as follows:

app
  /blog
    /[slug]
      page.tsx
    /@modal
      /(.)[slug]
        page.tsx
      /(.)summary
        /[slug]
          page.tsx
      default.tsx
    layout.tsx
    page.tsx

While I am on the blog/some-page page I can intercept <Link> to blog/summary/another-page but I cannot intercept <Link> to blog/another-page.

Expected Behavior

When on a page blog/some-page I would expect to be able to intercept <Link> to blog/another-page using a folder at blog/@modal/(.)[slug].

The purpose of this is to show summary modals for blog entries without navigating away from the current page.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 21
  • Comments: 25 (5 by maintainers)

Commits related to this issue

Most upvoted comments

FORWARD: #49614

Facing the same issue with the following structure; (v13.4.9)

.
└── [locale]/
    ├── (program)/
    │   ├── programma/
    │   │   └── [slug]/
    │   │       └── [id]/
    │   │           └── page.jsx
    │   └── program/
    │       └── [slug]/
    │           └── [id]/
    │               └── page.jsx
    ├── @modal/
    │   ├── (.)programma/
    │   │   └── [slug]/
    │   │       └── [id]/
    │   │           └── page.jsx
    │   └── (.)program/
    │       └── [slug]/
    │           └── [id]/
    │               └── page.jsx
    └── layout.jsx

And on top of that; the locale param becomes undefined when visiting the route

Still an ongoing issue it looks like.

So if I want section/25/edit you would expect to utilize this folder structure based on the Next convention in the docs:

section
- [id]
--- edit
---- page.tsx
- @modal
-- (.)[id]
--- edit
---- page.tsx
- layout.tsx (this contains the modal slot reference)
- default.tsx (this returns null so the modal doesn't get stuck when leaving the route)

But this doesn’t work. So instead we are left with what appears to be a few workarounds:

Work around 1:

You use (…) root segment as rodrigomf24 mentioned above: https://github.com/vercel/next.js/issues/52533#issuecomment-1735258750. Your folder structure now looks like so:

section
- [id]
--- edit
---- page.tsx
- @modal
-- (...)section
--- [id]
---- edit
----- page.tsx
- layout.tsx (this contains the modal @slot reference)
- default.tsx (this returns null so the modal doesn't get stuck when leaving the route)

This solution is more akin to an absolute path. It isn’t terrible if you don’t have to traverse a long root app path in your @slot.

In my case my section actually lives much further down the route hierarchy so this just isn’t worthwhile to nest segments so deeply under @modal. There is also the risk that it breaks if you add additional folder structures between the root and this without accounting for it.

Work around 2:

You use (…) instead of (…). This solution keeps that pesky section folder reference in our @modal but acts more like a relative path.

section
- [id]
--- edit
---- page.tsx
- @modal
-- (..)section
--- [id]
---- edit
----- page.tsx
- layout.tsx (this contains the modal @slot reference)
- default.tsx (this returns null so the modal doesnt get stuck when leaving the route)

Perhaps the biggest downside is it isn’t as intuitive as it could be because it feels like we are breaking what should be the standard Next convention in the original example because I’d expect (…) to move me up two segments for example. And it kinda is but it feels a bit confusing or not as clear as it could be since you have to maintain the parent folder.

Workaround 3:

You avoid having (.) on a dynamic segment. You can achieve this with a nested folder like so.

section
- overview
-- [id]
--- edit
---- page.tsx
- @modal
-- (.)overview
--- [id]
---- edit
----- page.tsx
- layout.tsx (this contains the modal @slot reference)
- default.tsx (this returns null so the modal doesnt get stuck when leaving the route)

IvanRomanovski touches on this in his comment: https://github.com/vercel/next.js/issues/52533#issuecomment-1735258750 even though his example is slightly different than mine.

This approach feels more in line with the Next convention but comes with a more polluted route path for users. It also might be difficult to come up with a nested segment for your use case.


Hopefully someone can tackle the core issue and allow dynamic segments to act as intercepted routes e.g) (.)[id]. For now these seem like the best solutions to working around this problem. If you spot any issues or typos with the solutions I’ve gathered to help save someone the hours of headache troubleshooting, let me know and I’ll edit them.

thanks to everyone here for helping me work through this problem!

@Netail try to add a error handler

.
└── [locale]/
    ├── (program)/
    │   ├── programma/
    │   │   └── [slug]/
    │   │       └── [id]/
    │   │           └── page.jsx
    │   └── program/
    │       └── [slug]/
    │           └── [id]/
    │               └── page.jsx
    ├── @modal/
    │   ├── (.)programma/
    │   │   └── [slug]/
    │   │       └── [id]/
    │   │           └── page.jsx
    │   └── (.)program/
    │       └── [slug]/
    │           └── [id]/
    │               └── page.jsx
    └── layout.jsx
    └── error.jsx <-- It solved for me
// error.jsx
'use client'

export default function Error() {
    return (
        <div>Oops, error!</div>
    )
}

I could not get route matching to work with routes that started with dynamic parameter, eg. /[lang]/, but it did work if it started with static name , e.g. /locale/[lang].

This did not work me:

[lang]
-- page.tsx
@modal
- (.)[lang]
-- [lang]
--- page.tsx

But this did:

lang
- [lang]
-- page.tsx
@modal
- (.)lang
-- [lang]
--- page.tsx

Its probably just a regex happening behind the scenes between path and (.)whatever, so I am not surprised dynamic matching does not work…

I achieved this repeating the structure inside modal for each language (I only have a small set of language, hope the same for you ATM)

- [lang]
- - page.tsx
- @modal
- - (.)en
- - - page.tsx
- - (.)it
- - - page.tsx

Hope it helps, little tricky but for now it does the job!

“next”: “13.4.19”,

I’m facing a slightly strange problem, I don’t know if I’m missing something.

I have a project that is already working normally, the dynamic routes together with route interception, everything is ok. The folder structure is as follows: Captura de tela 2023-11-03 112111

link: https://portfolio-renovatt.vercel.app/views/projects Note: the route is being intercepted, the background remains the same.

But I’m trying to use hidden routes, (views), putting them in parentheses, and following the same structure, following the same pattern, route interception doesn’t work correctly.

An error occurs where the original route and the intercepted route are called together.

I changed the structures a few times and found these two repositories, which I found in other issues: #49614 https://github.com/vetledv/repro-intercept/blob/main/src/app/(some-group)/%40modal/(.)photos/[id]/page.tsx https://github.com/Apestein/nextflix/blob/main/src/app/(main)/%40modal/(.)show/[id]/page.tsx

They are doing it like this, placing @modal inside (main), but when I do this, the route appears in the URL , however the screen is not changed, in this case my component does not appear, but if I update the page the original route happens, but just by placing (views) as the parent folder, the intercepted route happens, but with the same error as before , where the original route and the intercepted route are called together.

Captura de tela 2023-11-03 111949 Captura de tela 2023-11-03 111653

I don’t understand why this happens, can anyone help me?

“next”: “13.4.19”,

I’m facing a slightly strange problem, I don’t know if I’m missing something.

I have a project that is already working normally, the dynamic routes together with route interception, everything is ok. The folder structure is as follows: Captura de tela 2023-11-03 112111

link: https://portfolio-renovatt.vercel.app/views/projects Note: the route is being intercepted, the background remains the same.

But I’m trying to use hidden routes, (views), putting them in parentheses, and following the same structure, following the same pattern, route interception doesn’t work correctly.

An error occurs where the original route and the intercepted route are called together.

I changed the structures a few times and found these two repositories, which I found in other issues: #49614 https://github.com/vetledv/repro-intercept/blob/main/src/app/(some-group)/%40modal/(.)photos/[id]/page.tsx https://github.com/Apestein/nextflix/blob/main/src/app/(main)/%40modal/(.)show/[id]/page.tsx

They are doing it like this, placing @modal inside (main), but when I do this, the route appears in the URL , however the screen is not changed, in this case my component does not appear, but if I update the page the original route happens, but just by placing (views) as the parent folder, the intercepted route happens, but with the same error as before , where the original route and the intercepted route are called together.

Captura de tela 2023-11-03 111949 Captura de tela 2023-11-03 111653

I don’t understand why this happens, can anyone help me?

I managed to fix this problem, solution: I created the parallel route @modal outside the hidden route (views) and just placed the dynamic route following the same path, directly, without saying that it is inside the (views) route, this way it will actually be intercepted, this way I can normally call it /project/id.

I tested several ways, and I saw that calling the @modal route inside the (views) route was calling both routes at the same time, and the other way was calling the route but the component was not being used intercepted.

I hope this can help others with the same problem. Captura de tela 2023-11-07 153738 Captura de tela 2023-11-07 153930

I could not get route matching to work with routes that started with dynamic parameter, eg. /[lang]/, but it did work if it started with static name , e.g. /locale/[lang].

This did not work me:

[lang]
-- page.tsx
@modal
- (.)[lang]
-- [lang]
--- page.tsx

But this did:

lang
- [lang]
-- page.tsx
@modal
- (.)lang
-- [lang]
--- page.tsx

Its probably just a regex happening behind the scenes between path and (.)whatever, so I am not surprised dynamic matching does not work…

I’m having the same issue with the following folder structure:

post
├── @modal
│   │── default.js
│   └── (.)[slug]
│       └── page.js
├── [slug]
│   └── page.js
└── layout.js
└── default.js
└── page.js

The following does work:

post
├── @modal
│   │── default.js
│   └── (..)post/[slug]
│       └── page.js
├── [slug]
│   └── page.js
└── layout.js
└── default.js
└── page.js

But this intercepts the route from pages outside of the [post] route group as well, which is not desired for our use case.

@Netail Something weird is going on. I just pushed a new example to the linked reproduction repo with a similar structure to the one you posted. I don’t have route groups but I did add a [locale] base route to my original repro and then kept the rest of the structure the same. For me, this nested structure surprisingly works. I get the intercept and the modal for both the good case and the previous bad case.

Maybe you can pull my repo and play around with it to get your reproduction case? It seems there is some combination of factors that is causing the intercept to fail that isn’t totally clear.

Alright found the issue which triggers the issue; generateStaticParams. Created a seperate issue for this; #52880

Same issue here. I’m using next-intl and can’t show modal with intercepted routes yet.