Back to docs

Recipe: Modal-as-route pattern

Intercepting routes so a detail view opens as a modal overlay while keeping its own shareable URL.

The problem

You want a photo grid where clicking a photo opens a detail modal, but the modal must have its own route (/photos/42) so users can share or refresh it.

Folder layout

app/
  photos/
    page.tsx          ← grid (server component)
    layout.tsx        ← wraps children in modal shell
    [id]/
      page.tsx        ← full detail page (direct visit)
  @modal/
    (.)photos/
      [id]/
        page.tsx      ← intercepted modal view

Key mechanics

  • Parallel route @modal renders alongside children.
  • Intercepting route (.)photos catches navigation from the grid and swaps in the modal.
  • Direct visits or refreshes hit photos/[id]/page.tsx — the full standalone page.

layout.tsx sketch

export default function PhotosLayout({
  children,
  modal,
}: {
  children: React.ReactNode;
  modal: React.ReactNode;
}) {
  return (
    <>
      {children}
      {modal}
    </>
  );
}

Caveats

The modal slot is always rendered — guard it with a null check inside the intercepted page. Use router.back() to dismiss so the grid scroll position is preserved.