vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.17k stars 26.88k forks source link

[NEXT-1160] Clicking Links in intercepted routes does not unmount the interceptor route #49662

Closed mkarajohn closed 11 months ago

mkarajohn commented 1 year ago

Verify canary release

Provide environment information

Operating System:
      Platform: linux
      Arch: x64
      Version: #41~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 31 16:00:14 UTC 2
    Binaries:
      Node: 18.12.1
      npm: 8.19.2
      Yarn: 1.22.19
      pnpm: N/A
    Relevant packages:
      next: 13.4.2-canary.5
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0
      typescript: N/A

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

App directory (appDir: true), Routing (next/router, next/navigation, next/link)

Link to the code that reproduces this issue

https://codesandbox.io/p/github/mkarajohn/nextgram/draft/restless-leftpad?file=/components/frame/index.js

To Reproduce

  1. Open the codesandbox in a new separate tab in order to be able to see the actual browser URL
  2. Click on a photo in order to intercept the route and open the modal with the photo details.
  3. Instead of clicking on the overlay click the "go back" link instead, located just below the photo

Describe the Bug

When you click the Link and the URL changes to / the interceptor route remains mounted and does not go away.

Expected Behavior

I would expect that since the route was changed (via Link no less) the interceptor page should disappear once the route that it was intercepting was changed.

Screencast from 11-05-2023 04:23:04 ΜΜ.webm

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

NEXT-1160

feedthejim commented 1 year ago

hey @mkarajohn you can dismiss a modal like this via adding a catch-all route, see https://nextjs.org/docs/app/building-your-application/routing/parallel-routes#dismissing-a-modal

it doesn't work in your example because the router maintains routes already mounted until they their slot navigates

mkarajohn commented 1 year ago

@feedthejim Hi again, sorry, I am new to Next and maybe I am still misunderstanding, but the docs read:

you can dismiss the modal by calling router.back() or by using a Link component.

In the case described in the issue, I am using a Link component, the route changes, like it would if I had used router.back() but the change does not get reflected to the UI, unlike when using router.back(). Sorry for insisting, but the docs say "or by using a Link component" and this is clearly not the case here.

I added a catchAll route as per your suggestion, and I tried moving it around from app/ to app/@modal/ to app/@modal/(..)photos/ and see in which dir it will work as described and it did not work.

Either I am doing something very wrong, which is very likely and in which case I would appreciate if you could make the adjustment in the repro codesandbox so that I can see or explain it in a little more detail, or something is not working as described, or the docs are saying something that is not true.

Thank you

mkarajohn commented 1 year ago

I tried changing the Link with

    <button
      className="action"
      onClick={() => {
        router.push(`/`);
      }}
    >
      Close
    </button>

and this still does not work, while

    <button
      className="action"
      onClick={() => {
        router.back();
      }}
    >
      Close
    </button>

this works.

So, router.back() (moving back in the history stack) and router.push() (adding to the history stack) behave differently somehow for this case? They both change the url to / in the end. I am confused at how it's determined that in one case the route changed while in the other it's treated as if it did not.

resthedev commented 1 year ago

+1 I'm having this issue as well. Like OP, I've also tried using the the catch all route and putting default.tsx in various places but nothing works.

The only way for me to close the modal seems to be to use router.back() since using a Link to the previous page changes the URL but leaves the modal opened.

oliverjam commented 1 year ago

Yeah as far as I can tell catch all routes just don't work for clearing an intercepted route. See https://github.com/vercel/next.js/issues/48719 and https://github.com/vercel/next.js/issues/49531

vetledv commented 1 year ago

I am also having this issue. I found a workaround by grouping the intercepted route and placing the target route outside of this group.

mkarajohn commented 1 year ago

@vetledv Can you show how? (using something simple, like the reproduction repo in the OP)

@feedthejim considering other people are reporting this too, maybe this should be reopened?

vetledv commented 1 year ago

@mkarajohn I see now that it only works in my specific use page (I was linking to an equivalent of /hello in this example). Here is a repo that I believe should cover all the behaviours. This does very much not seem like intended behaviours. https://github.com/vetledv/repro-intercept

mkarajohn commented 1 year ago

@vetledv thanks for this, very informative, I played with it a little. Like you said, the grouping is the only thing that seems to be "fixing" this behaviour, nice find. The [...catchAll] route doesn't seem to do anything, can be deleted without effect.

And, again, like you said, it does not seem like "intended behaviour" considering you cannot return to / using a Link, since it's in the same group.

vetledv commented 1 year ago

I suppose the only reason it works is because it is no longer rendering the layout containing the modal. And yeah, the catchAll or any of the suggestions does not seem to work. Would like to see this reopened.

vetledv commented 1 year ago

Just thought of it now, I guess it's worth noting that you could make the layout a client component and conditionally render the modal based on the pathname to fix it entirely. Hopefully that could help for now until it's sorted out.

//(some-group)/layout.tsx
"use client";
import { usePathname } from "next/navigation";

export default function GroupLayout({
    children,
    modal,
}: {
    children: React.ReactNode;
    modal: React.ReactNode;
}) {
    const pathname = usePathname();
    const shouldShowModal = pathname.includes("/photos/");
    return (
        <div>
            {shouldShowModal && <div>{modal}</div>}
            <div>{children}</div>
        </div>
    );
}
thexpand commented 1 year ago

@feedthejim Why is this issue closed? This is still a problem.

Also, it is somehow related to what @mkarajohn is saying - router.push() doesn't seem to work either. Only router.back() does, but in my case, I don't want to use router.back(). When someone gets sent the link with the modal opened then they click on the button to close it, router.back() doesn't make sense. It would be better to call router.push() and navigate the user to the route behind the modal.

thexpand commented 1 year ago

UPDATE: I also noticed that the value, returned by the useSelectedLayoutSegment hook doesn't change when navigating away from the parallel route with router.push(). The only workaround currently is to make the layout a client component, and then write some additional logic to conditionally render the parallel route segment, based on the pathname, returned by the usePathname. I believe I saw this solution in some other thread but now I can't find it.

This issue is also related to the following one: https://github.com/vercel/next.js/issues/49731

lloydrichards commented 1 year ago

Has there been any update or progress on this? Ive just been implementing some parallel / intercepting routes for a model preview and discovered it not possible to navigate from the preview modal to the actual page. I think this is the same issue as this thread unless i'm missing something with how to navigate from within the modal 🤷

lmatteis commented 1 year ago

Working setup I have is here: https://github.com/lmatteis/nextgram

Apparently you need the right page.js that returns null at the right level to tell the slot to not render anything.

image
tiersept commented 1 year ago

This still does not work if...

The route you are intercepting and showing a modal is article/[articleId] and you have an overview page with a bunch of articles to pop a modal up on categories/[categoryId].

We then want to close that modal by doing a router.push('/categories/animals') since it's where you opened the modal from. Nothing happens. No unmount etc...

Why do we need this? Let's say you are in a modal and have a bunch of related articles. You are now routing from article/1 to article/2, then article/3. We want to close the modal entirely... A router.back() is not sufficient because we don't want to go from article/3 to article/2 but unmount the modal.

You could do a history.go(-3) which acts like a history back where -3 is the "nested" level within the modal since modal entry but that means you will have to track a separate history state which is flaky.

santiagoesteva commented 1 year ago

+1 The Modal slot mechanism is buggy. I have forced redirects or used router.back, while the url changes, the ModalInterceptor always get executed. Sometimes, the new route Im trying to go to gets properly executed and others it never does, as if it ended on the intercepted route and thats it.

tomas-c commented 1 year ago

Here's a tiny example that reproduces the issue:

https://codesandbox.io/p/sandbox/nifty-bessie-d9632z

  1. Go to /test/dashboard
  2. Click on "/test/dashboard/modal1" link and note that "Page for Modal1" is displayed correctly in the modal slot
  3. Click on "/test/dashboard" link and note that "Page for Modal1" is still displayed

Adding @modal/[...catchAll]/page.tsx as suggested in the docs here didn't help.

Don't want to use router.back due to the reasons described by @tiersept

lmatteis commented 1 year ago

Here's a tiny example that reproduces the issue:

https://codesandbox.io/p/sandbox/nifty-bessie-d9632z

There's something wrong with the setup. Hard navigating to test/dashboard/modal1 gives a 404. Mind sharing a git repo instead?

Overall though I think you just need to put a page.tsx that returns null directly inside the @modal folder.

tomas-c commented 1 year ago

@lmatteis

There's something wrong with the setup. Hard navigating to test/dashboard/modal1 gives a 404. Mind sharing a git repo instead?

Here's a repo with the files from the sandbox:

https://github.com/tomas-c/nifty-bessie-d9632z

Adding /app/test/dasbhoard/default.tsx makes hard navigation work.

Overall though I think you just need to put a page.tsx that returns null directly inside the @modal folder.

That's helped, thanks! I've created a branch update-1 on that repo with both changes applied:

https://github.com/tomas-c/nifty-bessie-d9632z/tree/update-1

It works nearly perfectly now.

There's one remaining issue for me with using this strategy for modals. It can be reproduced on the same update-1 branch):

If you hard navigate to /test/dashboard, click on /test/dashboard/modal1 and then click on /test/dashboard then the "children slot" stays the same throughout and renders "Page for dashboard" - great!

Screencast from 09-06-23 19:35:13.webm

But, if you hard navigate to /test/dashboard/modal1, click on /test/dashboard and then click on /test/dashboard/modal1 then the "children slot" switches from "Default for dashboard" to "Page for dashboard" and then back to "Default for dashboard"

Screencast from 09-06-23 19:38:13.webm

This means that in the second scenario, opening and closing a modal, makes main page component lose state.

lmatteis commented 1 year ago

@tomas-c It seems you want two pages to show this input field. Why not just put it in layout?

nicolasthy commented 1 year ago

Ran into the same issue and the usePathname technique mentioned by @vetledv worked for me. 🙏 🙏 🙏
Would be nice to have a proper way though

adidoes commented 1 year ago

I fixed this by making the modal itself a client component and checking if it should show based on the pathname

// /@modals/(.)posts/create/page.tsx
"use client";

import { usePathname } from "next/navigation";

export default function Dialog() {
  const pathname = usePathname();
  const shouldShowModal = pathname.includes("/posts/create");
  if (!shouldShowModal) return null;
  return (
    <div>my modal content</div>
  );
}

Definitely a bug though, I shouldn't have to do this. Are intercepting routes ready for production?

SeifA7mad commented 1 year ago

I fixed this by making the modal itself a client component and checking if it should show based on the pathname

// /@modals/(.)posts/create/page.tsx
"use client";

import { usePathname } from "next/navigation";

export default function Dialog() {
  const pathname = usePathname();
  const shouldShowModal = pathname.includes("/posts/create");
  if (!shouldShowModal) return null;
  return (
    <div>my modal content</div>
  );
}

Definitely a bug though, I shouldn't have to do this. Are intercepting routes ready for production?

This fix seems to create an issue (infinite loop), if I navigated from the modal to another page, then I re-open the modal again (now on the new page) when trying to dismiss the modal with router.back (as we usually do when clicks on the backdrop) the modal component get render again and again (infinite loop).

ibqn commented 1 year ago

Looks like it is somehow broken by design, hm. what if there are two intercepting routes, say, one to /sign-in and another one to /sign-up, which both lead to a modal dialog, and there is a link from signin modal to signup modal and in other direction as well, which is a common scenario))

ilkergonenc commented 1 year ago

I completely agree with the real-life scenarios, I was working on an authentication modal and found myself in exact same situation and been searching for a solution. At the end, I decided to take my own approach. My solution is to :

Here is my repo about the issue with an authentication modal for static and photos showcase for dynamic routes : ilkergonenc/nextjs-dismiss-router-modal-workaround

I am working on to improve this solution and transform the provider into a small, reusable package and I am open to any ideas, suggestions.

kimutaiRop commented 1 year ago

I know this is bit out of discussion but we also tried implementing this in our e-commerce but now we are trying to evaluate if the use case was right

in our case we made something that product details view would show product image and pricing details and some few other thing+add to cart button in the intercepted route

and in the actual route::

we would like like to see all the above + things like product description suggested products reviews..etc..

we tried finding way to expand from intercepted route to actual route and we could not find... (maybe something like (intercept=false so default behavior is intercept=true for backward compatibility) to the Link as a prop i don't know how easy that is of course to implement

That could maybe also be tackled together with this coz we even ended up adding router called mini-product/[id] to intercept then route.push could go to product/[id] but as the issue suggest that was not possible and also beats the all logic of our intended use case for instance

ilkergonenc commented 1 year ago

What I understand, it seems that you have implemented intercepting routes for the quick view modal to provide a convenient way to preview your products. However, this implementation is now causing an issue where you are unable to navigate to the actual product page because the interception takes precedence over regular navigation.

Actually, this situation presents an interesting real-life scenario. The concept of parallel and intercepted routes is relatively new, and determining their appropriate usage is a learning process for us to discover.

Right now, I'm not sure about any available options to bypass these intercepting routes as you mentioned. It would indeed be a great feature to have the flexibility and control over these routing options, allowing us to choose between different usage scenario by providing customizable routing options.

I will look for potential workarounds and if I come across any viable solutions, I will definitely let you know and add to my repo.

kimutaiRop commented 1 year ago

@ilkergonenc yea.. that is exactly what we are doing and issue facing.. yea.. will appreciate if you find workaround and let me know

ilkergonenc commented 1 year ago

Hİ @kimutaiRop , I searched on this around the blogs and I tried a lot of different workaround but I couldn't find a way to achieve this issue but It made me understand the intercepted and parallel routes conventions and how they work. For your case I believe the best solution is to build an old-school modal and with many other new features it would be still very easy and performant. With the new features we can make the modal client component but the set the quick view data as a server component and use new next/navigation useRouter hook to replace the url and achieve nearly the same outcome as the intercepted router offers.

Sls0n commented 1 year ago

Looks like it is somehow broken by design, hm. what if there are two intercepting routes, say, one to /sign-in and another one to /sign-up, which both lead to a modal dialog, and there is a link from signin modal to signup modal and in other direction as well, which is a common scenario))

I had the same problem. The only solution was to use a single Link component to intercept the initial route, either /sign-in or /sign-up, and then use anchor tags inside the modal instead of Link components. This way, the subsequent route won't be intercepted.

Also if you have two intercepting routes, /sign-in and /sign-up, both leading to modal dialogs, and you try to redirect to /sign-in after a successful signup, the signup modal keeps popping up, intercepting the signin page. So I don't think it's production ready yet.

Apestein commented 1 year ago

Should really mark it as experimental feature considering how unfinished intercepting/parallel routes are. I've wasted too much time trying to get this to work.

Apollo-XIV commented 1 year ago

Has there been any word of progress on this? this seems annoyingly buggy, especially for something outright recommended in the docs :(

kachkaev commented 1 year ago

@Apollo-XIV I was able make intercepted routes unmounted by creating dummy page routes under the @slot folder. For example, if we have page.tsx, foo/page.tsx and bar/[id]/page.tsx, creating @modal/page.tsx, @modal/foo/page.tsx and @modal/bar/[id]/page.tsx helps with unmounting. If we open @modal/(.)foobar/page.tsx and then navigate away from the intercepted /foobar route, Next.js renders another route in the @modal slot.

You can find an example in https://github.com/vercel/next.js/issues/53170. It’s a bug report related to multiple route groups, but if you have just one group with one layout, the trick with dummy routes might do the unmounting for you.

Apollo-XIV commented 1 year ago

@Apollo-XIV I was able make intercepted routes unmounted by creating dummy page routes under the @slot folder. For example, if we have page.tsx, foo/page.tsx and bar/[id]/page.tsx, creating @modal/page.tsx, @modal/foo/page.tsx and @modal/bar/[id]/page.tsx helps with unmounting. If we open @modal/(.)foobar/page.tsx and then navigate away from the intercepted /foobar route, Next.js renders another route in the @modal slot.

You can find an example in #53170. It’s a bug report related to multiple route groups, but if you have just one group with one layout, the trick with dummy routes might do the unmounting for you.

You are an absolute lifesaver, you've easily saved me hours of finding a workaround. Thank you sm :))

apetta commented 1 year ago

This is still a big issue. Working off the oficial nextgram example for reference, if you want to link away to a different part of the app from the intercepted, parallel route modal you're just stuck with it wherever you go...

Catch-all pattern suggested by the docs seems to do nothing. Placing a null returning page.tsx like @lmatteis suggested works if you're linking back to / but if you're linking somewhere else in the app like /dashboard then the modal just stays open...

apetta commented 1 year ago

This is still a big issue. Working off the oficial nextgram example for reference, if you want to link away to a different part of the app from the intercepted, parallel route modal you're just stuck with it wherever you go...

Catch-all pattern suggested by the docs seems to do nothing. Placing a null returning page.tsx like @lmatteis suggested works if you're linking back to / but if you're linking somewhere else in the app like /dashboard then the modal just stays open...

Okay so, in case it helps anyone, the only way to handle this right now is to isolate the @slot and the layout.tsx which uses it in it's own space (either nested or using a route group), so that this layout.tsx does not cover the pages where your modal is not required. The nextgram example isn't good for this because it's the root layout that uses the slot (modal) so all pages you make in there will be covered by it.

extrabright commented 1 year ago

I am trying to recreate the image gallery built in Unsplash:

It works fine, back/forward buttons are working as expected, but when I click on the close button or overlay I cannot find a way to close all modals at once. Right now, you have to click the amount of times you clicked to open each image to dismiss it.

For this, I created a route provider which stores the last pathname I had before opening a modal. When I want to close the modal, I simply used route.push(pathname). But, sadly, the push method doesn't work as expected. I thought it would have the effect of router.back().

Shouldn't the push method work that way?

extrabright commented 1 year ago

Still not figured it out so any help is appreciated. Meanwhile I managed to get the behaviour I needed using a context provider which tracks the amount pathname changes made inside the modal. When I want to close the modals, I loop the counter, smth like this, since router.back() is the only way to close this intercepted routes:

if (clickCount === 0) {
      router.back()
    }
    else {
      for (let i = 0; i < clickCount; i++) {
        router.back()
      }
    }

    clearClickCount()

So far it works pretty good with no glitches. However, I am hoping for official support on this issue.

alessandrojcm commented 1 year ago

I also have an issue with intercepting and parallel routes: I've got a creation form that can be opened in a modal, using an slot with an interceptor route (so the user can access said form on any point within the app) but I've also got a normal route in case the user decides to bookmark the URL or visit it directly typing it in the browser.

My issue us that navigating to the URL by typing it directly in the browser bar causes the modal to render on top of the actual route. Not sure if I am doing something wrong here but are interceptor routes not supposed to be triggered by client navigation (ie using or router.push) as opossed by visiting the URL directly?

kght6123 commented 1 year ago

I also had a headache with this problem, but solved it by using dynamic routes as follows. (Sorry it's hard to understand as it's code for a service I'm working on)

スクリーンショット 2023-09-01 23 43 28

The complete source code is posted next. https://github.com/kght6123/ors/tree/main/app/(dataEntry) I don't think this will be a complete solution, but it will be a temporary measure.

smuk3c commented 1 year ago

Having same issue. Wasn't able to fix this with using Link - went back to router.back till it works

AveshLutchman commented 1 year ago

Same issue. In my case I'm trying to redirect to another page using router.push() however, while it redirects to the correct page, the modal stays up. My solution to this for now is just checking the current pathname like other users suggested.

vetledv commented 1 year ago

Any update on this? This issue still makes the feature completely unusable for many use cases, or extremely convoluted at minimum with all the workarounds needed.

I have also found another issue that simply breaks the router history if stumbled upon. My previous repo that was linked in this thread a while back has been updated to latest 13.4.20-canary.31. Repo: https://github.com/vetledv/repro-intercept

To reproduce this issue:

I see now that this must be similar to @extrabright 's issue. Maybe a feature to pop all intercepted routes would be beneficial? Edit: Specifying if you want to intercept or not could be nice maybe. This issue would be avoided if it wasn't possible to intercept from an intercepted route.

Intercepted routes is perfect for so many things I want to do, so I would love to see these issues ironed out. I would probably have no chance, but if someone can point me to the right places I would love to at least try to help out here.

resthedev commented 1 year ago

Intercepted routes is perfect for so many things I want to do, so I would love to see these issues ironed out. I would probably have no chance, but if someone can point me to the right places I would love to at least try to help out here.

I agree. One of the biggest selling points of migrating to NextJS app/ directory for me was the intercept/parallel routes feature. It's a bit disheartening right now to have fully migrated to it and so many things are broken.

If these aren't fixed, I will ultimately just have to go back to the old way of doing things (parallel/intercept routes with useSearchParams -> regular React state/manually change URL).

Apestein commented 1 year ago

You don't have to use Link to close modal, see my implementation of intercepting modal Live: https://nextflix-blush.vercel.app/

brandensilva commented 1 year ago

@Apollo-XIV I was able make intercepted routes unmounted by creating dummy page routes under the @slot folder. For example, if we have page.tsx, foo/page.tsx and bar/[id]/page.tsx, creating @modal/page.tsx, @modal/foo/page.tsx and @modal/bar/[id]/page.tsx helps with unmounting. If we open @modal/(.)foobar/page.tsx and then navigate away from the intercepted /foobar route, Next.js renders another route in the @modal slot.

You can find an example in #53170. It’s a bug report related to multiple route groups, but if you have just one group with one layout, the trick with dummy routes might do the unmounting for you.

This is exactly what I needed to do to workaround this without additional code. I was missing @modal/page.tsx but had a page.tsx file in all subsequently nested @modal routes. e.g) @modal/(.)add/page.tsx, @modal/(.)edit/page.tsx.

It is not documented in Next docs this is required which is perhaps the biggest culprit that could easily be updated to ease a lot of developer pain from this.

So in summary for my case, the @modal needed a page.tsx null return similar to default.tsx to wipe out the intercepted route with router.push() after saving in the modal without needing router.back() or any other code workarounds.

If you aren't use .tsx but .ts or .js in this file structure you have to have them match all the way through for it to work too.

IvanRomanovski commented 1 year ago

I am not convinced this is really a bug. If something is mounted into a slot I don't think it should be unmounted if slot still exists, which is the case in such setups where modal slot is on root level and one navigates from intercepted route /images/[id] to root /. Imho appropriate solution is to just move slot one level down. Eg. in a hypothetical case of a blog with expandable images, that would mean going from this:

|-@modal
|-- (.)photos
|-blog
|-photos

to this

|- blog
|-- @modal
|--- (...)photos
|-photos
partridge1307 commented 1 year ago

Still not figured it out so any help is appreciated. Meanwhile I managed to get the behaviour I needed using a context provider which tracks the amount pathname changes made inside the modal. When I want to close the modals, I loop the counter, smth like this, since router.back() is the only way to close this intercepted routes:

if (clickCount === 0) {
      router.back()
    }
    else {
      for (let i = 0; i < clickCount; i++) {
        router.back()
      }
    }

    clearClickCount()

So far it works pretty good with no glitches. However, I am hoping for official support on this issue.

Can you tell me how can you do this?

SpBills commented 1 year ago

Okay, I have done some significant work here to make this work for me. Here is the context:

I will do everything in my power to stay away from client-side state management. I love using routes to manage all of my locational state (including modals). I noticed that whenever I was doing this, redirect("/profile") did not redirect me back! Instead, my modals stayed open.

Using hints from above, I discovered that what is really important is the layout on which your modal is being rendered. That is, you should be rendering your slot in the layout that minimally contains your intercept routes.

So, if I have a route structure like such:

/profile
/profile/group/[id]/edit // A modal.
/profile/group/[id]/delete // A modal.

I really only want to be slotting in the minimal containing folder. Here, that would be group. Let's discuss why we want this.

Why Only Grab the Minimum?

Let's start by describing our file structure. Here, I lay out what we do not want.

/profile
/profile/layout.tsx
/profile/@dialog/(.)group/[id]/edit 
/profile/@dialog/(.)group/[id]/delete

The layout.tsx here looks something like...

import { ReactNode } from "react";

export default function Layout({
  children,
  dialog,
}: {
  children: ReactNode;
  dialog: ReactNode;
}) {
  return (
    <>
      {children}
      {dialog}
    </>
  );
}

Now, if I am in the edit modal and I redirect back to /profile, am I changing the layout properties? No. I might argue that I should be, but right now you are not. Instead, you are just saying "rerender the layout in the state you already have", which still contains your slot.

How to grab the minimum

I'll keep the answer simple: we want to use route groups.

/profile
/profile/layout.tsx
/profile/(modal)/layout.tsx
/profile/(modal)/@dialog/(.)group/[id]/edit 
/profile/(modal)/@dialog/(.)group/[id]/delete

There are some nuances here. First, remember that we do not want the profile/layout.tsx to be rendering the @dialog slot. Do not do that. Instead, use the (modal)/layout.tsx to render your dialog slot. Second, do not use (modal)/layout.tsx to render children either. Only use it to render your dialog slot.

// /profile/layout.tsx
import { ReactNode } from "react";

export default function Layout({
  children,
}: {
  children: ReactNode;
}) {
  return (
    <>
      {children}
    </>
  );
}
// (modal)/layout.tsx
import { ReactNode } from "react";

export default function Layout({
  dialog,
}: {
  dialog: ReactNode;
}) {
  return (
    <>
      {dialog}
    </>
  );
}

Summary

Like @IvanRomanovski said- this is likely not a bug, we're just not using the system right. Now, that's arguably a bad take from a DX perspective and I do think that this should be changed. But hopefully this sheds some light onto the complexities behind this.