Type something to search...
Next.js Intercepting Routes

Next.js Intercepting Routes

There's a specific kind of navigation almost every app eventually needs: clicking a photo in a feed should open it in a modal, right there, without leaving the feed behind — but sharing a direct link to that same photo, or refreshing the page while it's open, should load the full standalone photo page instead. Client-side modal libraries handle the first half fine and completely fail at the second — a modal that's just local component state has no idea what to render on a fresh page load, because there's no state to restore.

Intercepting Routes is the App Router convention built to close that gap. It lets one route "intercept" the render of another route from within the current layout — masking the URL to look like the intercepted destination, while the actual navigation context (the layout, the surrounding UI) stays exactly where it was.

The Core Idea

Intercepting routes allows you to load a route from another part of your application within the current layout.

The canonical example: clicking a photo in /feed should display /photo/123's content as an overlay on top of the feed — not by literally leaving /feed and loading a new page, but by intercepting the client-side navigation to /photo/123 and rendering its content inline. The browser's address bar reflects /photo/123 (so the URL is real and shareable), but the user visually never left /feed.

Critically, this behavior is navigation-type-dependent. A soft, client-side navigation (clicking the link from within the feed) triggers the interception and shows the overlay. A hard navigation — someone pasting that /photo/123 URL directly, or hitting refresh while it's open — bypasses interception entirely and renders the full, standalone photo page. Same URL, two different render paths, chosen automatically based on how the user arrived there.

Convention: The Matcher Syntax

Intercepting routes use a small set of relative-path-style matchers, based on route segments rather than raw folder nesting:

  • (.) — matches segments on the same level
  • (..) — matches segments one level above
  • (..)(..) — matches segments two levels above
  • (...) — matches segments from the root app directory

To intercept the photo segment from within feed, you'd create a (..)photo directory relative to feed — one level up, since photo and feed are siblings under the same parent.

The good-to-know that trips people up here: these matchers are based on route segments, not literal file-system depth. Specifically, they do not count @slot folders from Parallel Routes as a level. If a slot sits between your intercepting folder and its target on disk, don't count it when choosing between (.), (..), and (..)(..) — count only the route segments, ignoring slot folders entirely. This is exactly why the modal pattern below can use (.) — same-level matching — even though the file-system path involves an extra @modal folder that a naive folder-depth count would suggest needs (..).

Worked Example: Modals

Intercepting Routes is designed to be combined with Parallel Routes specifically for the shareable-modal pattern. Together they solve several problems that plain client-side modals structurally cannot:

  • Making modal content shareable through a URL.
  • Preserving context on refresh, instead of the modal silently closing.
  • Closing on backward navigation rather than navigating to whatever the previous route happens to be.
  • Reopening on forward navigation, restoring the modal exactly as it was.

Picture a gallery where a user can open a photo modal via client-side navigation, or land on /photo/123 directly from a shared link — with both paths ultimately backed by the same underlying route.

Because @modal is a slot (not a route segment), the path from the gallery's parent layout down to photo is only one segment level, even though it's two folder levels on disk. That's why the matcher here is (..)photo, not (..)(..)photo — the slot doesn't count toward the segment distance:

app/
├── layout.tsx
├── @modal/
│   └── (..)photo/
│       └── [id]/
│           └── page.tsx      ← intercepted, renders inside a <Modal>
├── photo/
│   └── [id]/
│       └── page.tsx           ← the real, standalone page
└── gallery/
    └── page.tsx

The full step-by-step build of this pattern — the default.js fallback, wiring the slot into the parent layout, and the specific technique for reliably closing the modal on arbitrary navigation — lives in the Parallel Routes documentation, since the modal-closing mechanics are really a Parallel Routes concern once the interception itself is in place. What's worth understanding here, at the Intercepting Routes level specifically, is just the matcher choice: (..) because photo sits one route segment above the slot, regardless of the extra folder nesting the slot itself introduces.

Choosing the Right Matcher

Since the matcher choice depends on route segments rather than raw folders, it's worth working through a couple of scenarios explicitly rather than trying to memorize the rule abstractly.

Same-level interception — intercepting a sibling segment from within another segment at the identical depth:

app/
├── feed/
│   └── (.)photo/
│       └── [id]/page.tsx
└── photo/
    └── [id]/page.tsx

Use (.) when the segment you're intercepting is a direct sibling of the folder your interception lives in.

One level up — the feed/photo example from the intro, where the interception happens from a route nested one segment beneath where the target segment actually lives:

app/
├── feed/
│   └── page.tsx
├── (..)photo/            ← at the root, one level above /feed
│   └── [id]/page.tsx

From the app root, regardless of depth(...) is the escape hatch for when you're intercepting from deep inside nested routes but want to match against a route defined at the very top of your app directory, no matter how many segments separate the two:

app/
├── settings/
│   └── profile/
│       └── (...)photo/      ← still matches app/photo, from three segments deep
│           └── [id]/page.tsx
└── photo/
    └── [id]/page.tsx

The rule of thumb: count route segments between where you're intercepting from and where the target route actually lives, treating slots as invisible, and pick the matcher that expresses that distance.

Beyond Modals

The docs explicitly call out that photo galleries and login modals aren't the only use case — the same interception mechanic applies anywhere you want "preview this route inline, but treat the direct URL as the full experience." A shopping cart that slides in from the side while still being addressable as its own route. A quick-view product panel that overlays a product listing page. A comment thread that expands inline from a feed but is also a real, linkable permalink on its own. The common thread across all of these: the content genuinely deserves its own URL and its own standalone page, but the common-case navigation to it should feel like staying in place rather than a full page transition.

Key Takeaways

MatcherMatches
(.)Same segment level
(..)One segment level above
(..)(..)Two segment levels above
(...)From the app root, regardless of depth
ConceptDetail
Navigation-dependent renderingSoft navigation triggers interception; hard navigation (refresh, direct link) renders the real standalone page
Segment-based, not folder-based@slot folders from Parallel Routes don't count toward matcher distance
Typical pairingAlmost always combined with Parallel Routes for the shareable-modal pattern
Not modal-exclusiveAny "preview inline, but also a real linkable page" UI qualifies — carts, quick views, expandable threads

Intercepting Routes solves a problem that's genuinely hard to get right with client-side state alone: making an inline preview and a full standalone page be, mechanically, the same route, with the framework deciding which rendering path to take based on how the user actually arrived. Once the matcher syntax clicks — count segments, ignore slots — the rest of the convention is just ordinary file-system routing wearing a different hat.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper