Intercepting Routes in Next.js: Building Modal Routes

Intercepting routes load one route inside the current layout while masking the URL, which is what lets a photo open as a modal instead of a new page.

7 min read

Next.js intercepting routes load a route from elsewhere in the app inside the current layout, while keeping the browser URL pointed at that route's real address. This is the mechanism behind a common pattern: clicking a photo in a feed opens it as a modal, but sharing that same URL loads the full photo page instead.

texttext
app/
  feed/
    page.tsx
    (.)photo/
      [id]/
        page.tsx

The (.)photo folder intercepts the photo segment for navigations that start from the feed. Clicking through from the feed renders the intercepted version inside the feed's layout; loading the photo URL directly renders the normal, non-intercepted page instead. The dynamic id segment inside the intercepted folder works exactly like any other dynamic route, covered in dynamic routes in Next.js.

The dot conventions

The prefix tells Next.js how many segment levels up the real route sits, counted in route segments rather than file-system folders.

PrefixMatches
(.)Same segment level
(..)One segment level above
(..)(..)Two segment levels above
(...)From the root of the app directory

A @slot folder from a parallel route does not count as a level, since it is not a route segment either. This is why a modal slot two folders deep on disk can still intercept a route that is only one segment away in the URL.

Interception only happens on soft navigation

Clicking a link that triggers a client-side transition is what activates the interception. A hard navigation, such as a full page reload or pasting the URL directly into the address bar, always renders the real, un-intercepted route. This is intentional: it is what keeps a modal shareable and reload-safe, since the link a visitor copies always resolves to a real, standalone page.

Building a modal with intercepting and parallel routes

An intercepting route alone has nowhere obvious to render, so building an actual modal pairs it with a parallel route slot. The slot supplies the layout position for the modal, and the intercepting folder inside that slot supplies the content shown during a soft navigation.

App.tsxApp.tsx
// app/@modal/(.)photo/[id]/page.tsx
export default function InterceptedPhoto() {
  return <p>Modal photo view</p>;
}

The parent layout renders this slot alongside the page's regular children, and a default.tsx file in the same slot returns null so nothing shows when no photo is being viewed. The full step-by-step slot setup is covered in parallel routes in Next.js.

Common mistake

Expecting the modal to survive a page refresh is a common point of confusion. Because interception depends on client-side navigation state, refreshing always falls back to the real route, which is the correct and expected behavior for a URL meant to be shareable rather than a bug to work around.

Rune AI

Rune AI

Key Insights

  • A folder prefixed with (.) intercepts a route at the same segment level.
  • (..) intercepts one level above, and (...) intercepts from the root.
  • Interception only happens on soft, client-side navigation.
  • A hard navigation or refresh renders the real route instead of the intercepted view.
  • Intercepting routes need a parallel route slot to actually render as a modal.
RunePowered by Rune AI

Frequently Asked Questions

Do intercepting routes work without parallel routes?

Technically a route can be intercepted on its own, but building an actual modal needs a parallel route slot too, since the layout needs somewhere to render the intercepted content alongside the page underneath it.

Why does the modal disappear on refresh and show a full page instead?

Refreshing is a hard navigation. Next.js cannot recover client-side state on a hard navigation, so it renders the real route the URL points to instead of the intercepted version.

Are the dot conventions based on folders or on URL segments?

They are based on route segments, not the file-system. A @slot folder from a parallel route does not count as a level when counting how many dots you need.

Conclusion

An intercepting route loads another part of the app inside the current layout while keeping the URL pointed at that route's real address. Combined with a parallel route slot, this is what makes a shareable, refresh-safe modal possible.