How to Fix: Error on dynamic parallel route

6 min read

Clicking the link inside /profile-list breaks because the app is mixing a dynamic segment with a parallel route slot in a way Next.js cannot resolve during navigation. The result is a routing error that appears only when the client tries to transition into the dynamic route tree.

Understanding the Root Cause

This issue happens because parallel routes in the Next.js App Router are not ordinary nested folders. A folder such as @modal or any other slot name is a named render slot, not part of the URL path. When a dynamic route like [id] is placed or referenced incorrectly across that structure, Next.js may fail to build the expected routing tree for the client transition.

In the reproduction app, navigation starts from /profile-list and then moves into a route that depends on both:

  • a dynamic segment such as [id], and
  • a parallel route used to render alternate UI, often a modal or secondary view.

The bug appears when the link targets a path that assumes the slot behaves like a real URL segment. It does not. Parallel route folders do not contribute to the pathname. They are mounted by the parent layout through props such as children and a named slot prop.

That means two things must stay aligned:

  1. The filesystem structure must define the dynamic page in the correct route segment.
  2. The parent layout must explicitly render the matching parallel slot.

If either side is missing, navigation can fail with route resolution errors, unexpected 404 behavior, or runtime crashes during client-side transitions.

A common example of the mistake looks like this conceptually:

app/profile-list/@detail/[id]/page.tsx

If the parent layout does not render the detail slot, or if the link points to a pathname that assumes @detail is part of the URL, Next.js cannot reconcile the route tree properly.

Step-by-Step Solution

The fix is to structure the route so the URL path and the parallel slot each have clear roles:

  • Put the real navigable path in normal route segments.
  • Use the parallel route only as a rendering target.
  • Make sure the parent layout.tsx accepts and renders the slot prop.

1. Define the main route normally

Your list page should remain a standard route:

app/profile-list/page.tsx

Example:

import Link from 'next/link';

export default function ProfileListPage() {
  return (
    <div>
      <h1>Profiles</h1>
      <Link href="/profile-list/1">Open profile 1</Link>
    </div>
  );
}

2. Create the dynamic route in the real URL tree

The actual page for a profile should exist in a standard dynamic segment:

app/profile-list/[id]/page.tsx
type Props = {
  params: {
    id: string;
  };
};

export default function ProfileDetailPage({ params }: Props) {
  return <div>Profile ID: {params.id}</div>;
}

This ensures direct access to /profile-list/1 works even without any parallel UI.

3. Add a parallel route only if you need alternate rendering

If the goal is to show the profile in a modal while keeping the list visible, define a slot under the same parent segment:

app/profile-list/layout.tsx
app/profile-list/@modal/[id]/page.tsx
app/profile-list/@modal/default.tsx

The parent layout must render the named slot:

type Props = {
  children: React.ReactNode;
  modal: React.ReactNode;
};

export default function ProfileListLayout({ children, modal }: Props) {
  return (
    <>
      {children}
      {modal}
    </>
  );
}

The default file prevents the slot from crashing when nothing is mounted into it:

export default function Default() {
  return null;
}

4. Use an intercepted or matching route pattern correctly if this is meant to open as a modal

If the user clicks from the list and should see a modal version of the dynamic page, the modal route usually needs an intercepting route pattern rather than a plain parallel slot alone.

A common structure is:

app/profile-list/page.tsx
app/profile-list/[id]/page.tsx
app/profile-list/@modal/(.)[id]/page.tsx
app/profile-list/@modal/default.tsx
app/profile-list/layout.tsx

The intercepted route (.)[id] tells Next.js to render the dynamic route inside the modal slot during client navigation from the current level, while still allowing full-page rendering on direct visits to /profile-list/1.

Example modal route:

type Props = {
  params: {
    id: string;
  };
};

export default function ProfileModal({ params }: Props) {
  return (
    <div role="dialog" aria-modal="true">
      <h2>Quick View</h2>
      <p>Profile ID: {params.id}</p>
    </div>
  );
}

Your link should always target the actual pathname:

<Link href="/profile-list/1">Open profile</Link>

Do not link to a path containing the slot folder name such as @modal. That folder is not part of the browser URL.

6. Verify the route contract

After restructuring, test both behaviors:

  1. Open the profile list page and click the link.
  2. Open the destination URL directly in a new tab.

If both work, the route tree is now consistent:

  • Client navigation can render the slot version.
  • Hard refresh or direct access can render the standard dynamic page.
app/
  profile-list/
    layout.tsx
    page.tsx
    [id]/
      page.tsx
    @modal/
      default.tsx
      (.)[id]/
        page.tsx

Common Edge Cases

1. Missing default.tsx in the slot
If a parallel route exists without default.tsx, refreshing or loading sibling pages may fail because Next.js has no fallback UI for that slot.

2. Layout does not accept the slot prop
If your folder is named @modal but layout.tsx only renders children, the slot content is never mounted.

3. Wrong intercepting syntax
Using [id] instead of (.)[id] inside the slot changes the routing behavior. For modal-style navigation, the intercepting pattern is usually the correct approach.

4. Linking to the wrong pathname
The URL should represent the real route, not the slot folder. Never include @slotName in href.

5. Direct visit works, client navigation fails
This usually means the standard dynamic page exists, but the parallel or intercepted route tree is incomplete or mismatched.

6. Client component state resets unexpectedly
Parallel routes can remount parts of the tree depending on layout boundaries. If state disappears, check where the stateful component lives and whether the transition crosses a different layout segment.

FAQ

Because client-side navigation uses the App Router’s internal route tree resolution. A direct page load may succeed if the normal dynamic route exists, but the client transition can still fail when the parallel route or intercepted route is misconfigured.

Do parallel route folder names appear in the browser URL?

No. A folder like @modal is a slot identifier, not a path segment. You render it in layout.tsx, but you never navigate to it directly as part of the URL.

When should I use an intercepting route instead of a plain parallel route?

Use an intercepting route when you want the same destination URL to behave differently during in-app navigation, such as opening a modal from a list while still allowing a full-page render on direct access.

The practical fix for this issue is simple: keep [id] in the real route path, render the slot from the parent layout, add default.tsx, and use an intercepting route if the dynamic page should appear as a modal during navigation. That removes the route mismatch causing the dynamic parallel route error.

Leave a Reply

Your email address will not be published. Required fields are marked *