Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. Next.js Dictionary
  3. Intercepting Routes

Intercepting Routes

Since: Next.js 13(2022)

In the App Router of Next.js, you can define folder names using notations such as (.) or (..) to intercept normal page transitions and display a different component instead. This is called an intercepting route. A typical use case is displaying a detail view as a modal when a link in a list page is clicked — rather than a full page navigation — while keeping the URL correctly updated and maintaining the current screen.

Notation Reference

NotationDescription
(.)Intercepts a segment at the same level.
(..)Intercepts a segment one level up.
(..)(..)Intercepts a segment two levels up.
(...)Intercepts a segment from the root (top level) of the app directory.

Sample Code

An implementation example where clicking a link on a photo list page (/photos) intercepts the full page navigation to /photos/[id] and displays it as a modal instead. The URL correctly changes to something like /photos/1, but the screen stays on the list with the modal overlaid. When the URL is accessed directly or the page is reloaded, the normal detail page is rendered.

// Directory structure
// app/
// ├── @modal/                        // Modal slot for parallel routes
// │   ├── (.)photos/[id]/
// │   │   └── page.js                // Intercepts /photos/[id]
// │   └── default.js                 // Initial state when no modal is shown
// ├── photos/
// │   ├── page.js                    // → /photos (photo list page)
// │   └── [id]/
// │       └── page.js                // → /photos/[id] (photo detail page)
// └── layout.js                      // Layout that receives the @modal slot
// app/layout.js
// Root layout
// Receives the @modal slot and renders it alongside the normal content

export default function RootLayout({ children, modal }) {
  return (
    <html lang="en">
      <body>
        {/* Normal page content */}
        {children}

        {/* Modal slot — contains the modal during interception */}
        {modal}
      </body>
    </html>
  );
}
// app/photos/page.js
// Page that displays a list of photos
// Clicking a <Link> triggers navigation to /photos/[id],
// but the intercepting route displays the modal instead

import Link from 'next/link';

// Dummy data for the sample
const photos = [
  { id: '1', title: 'Ocean photo', src: '/img/sea.jpg' },
  { id: '2', title: 'Mountain photo', src: '/img/mountain.jpg' },
  { id: '3', title: 'City photo', src: '/img/city.jpg' },
];

export default function PhotosPage() {
  return (
    <main>
      <h1>Photo List</h1>
      <ul style={{ display: 'flex', gap: '16px', listStyle: 'none' }}>
        {photos.map(function(photo) {
          return (
            <li key={photo.id}>
              {/* Clicking navigates to /photos/1, etc. */}
              {/* The intercepting route is active, so the modal opens */}
              <Link href={'/photos/' + photo.id}>
                <img src={photo.src} alt={photo.title} width={200} height={150} />
                <p>{photo.title}</p>
              </Link>
            </li>
          );
        })}
      </ul>
    </main>
  );
}
// app/@modal/(.)photos/[id]/page.js
// Intercepts client-side navigation to /photos/[id]
// and renders it as a modal component
// (.) indicates that the same-level segment (photos) is being intercepted

'use client';

import { useRouter } from 'next/navigation';

export default function PhotoModal({ params }) {
  // useRouter is used to close the modal (go back to the previous page)
  const router = useRouter();

  return (
    <div
      style={{
        position: 'fixed',
        inset: 0,
        background: 'rgba(0,0,0,0.7)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        zIndex: 100,
      }}
      // Clicking the overlay closes the modal
      onClick={function() { router.back(); }}
    >
      <div
        style={{ background: '#fff', padding: '24px', borderRadius: '8px' }}
        // Prevents clicks inside the modal from propagating to the outside
        onClick={function(e) { e.stopPropagation(); }}
      >
        <h2>Photo ID: {params.id}</h2>
        <img
          src={'/img/photo-' + params.id + '.jpg'}
          alt={'Photo ' + params.id}
          width={600}
          height={400}
        />
        {/* Close button returns to the list page */}
        <button onClick={function() { router.back(); }}>Close</button>
      </div>
    </div>
  );
}
// app/photos/[id]/page.js
// Detail page displayed when the URL is accessed directly or on reload
// The intercepting route is only active during client-side navigation,
// so this file must always be present

export default async function PhotoDetailPage({ params }) {
  // In a real application, photo data would be fetched from an API or database
  const photoId = params.id;

  return (
    <main style={{ padding: '40px' }}>
      <h1>Photo Detail</h1>
      <img
        src={'/img/photo-' + photoId + '.jpg'}
        alt={'Photo ' + photoId}
        width={800}
        height={600}
      />
      <p>Photo ID: {photoId}</p>
      {/* Link to return to the list */}
      <a href="/photos?lang=en">&larr; Back to list</a>
    </main>
  );
}
// app/@modal/default.js
// Default file for the normal state when no modal is displayed
// Without this file, the modal slot becomes undefined and causes an error
// Returning null explicitly indicates that nothing should be rendered

export default function ModalDefault() {
  return null;
}

Overview

Intercepting routes work by defining folder names that use special notations such as (.). They intercept route transitions caused by client-side navigation (<Link> clicks or router.push()) and display a different component instead of the intended page. On the other hand, direct URL access or page reload does not trigger interception, and the normal page is rendered. By leveraging this characteristic, you can implement experiences such as displaying a photo from a social media feed in a modal while correctly sharing the URL.

Intercepting routes are almost always used in combination with Parallel Routes. In the example above, a parallel slot called @modal is prepared, and the intercepting route component is placed there, allowing the modal to overlay the list page while keeping it visible.

The hierarchy in the notation is based on the App Router's route segment hierarchy, not the depth of the file system. (.) means the same segment level, (..) means one level up, and (...) means from the root of the app directory. For information on defining route groups, see Route Groups.

Common Mistakes

Common Mistake 1: Forgetting default.js causes an error in the modal slot

When a parallel route slot (e.g., @modal) is defined, a file that returns something is needed even in the normal state when no modal is displayed. Without default.js, the slot's content becomes undefined when no interception is occurring, causing an error.

ng_example
// NG: directory structure where the @modal slot has no default.js

// app/
// ├── @modal/
// │   └── (.)photos/[id]/
// │       └── page.js    // Only the intercepting route, no default.js
// ├── photos/
// │   └── page.js
// └── layout.js
Error: Missing default export in @modal slot.
The default export is required when using Parallel Routes.

Create app/@modal/default.js and define a default export that returns null.

ok_example.js
// app/@modal/default.js
// Default file for the normal state when no modal is displayed
// Returning null explicitly indicates that nothing should be rendered

export default function ModalDefault() {
  return null;
}

Common Mistake 2: Wrong hierarchy in (.) and (..) causes interception to fail

The notation in intercepting routes is based on the App Router's route segment hierarchy, not the depth of the file system. (.) means the same segment level, and (..) means one level up. Placing files in the wrong location results in no interception occurring and normal page navigation taking place instead.

ng_example
// NG: @modal is directly under app, the interception target is app/photos/[id],
// but (..) is used (the correct notation is (.))

// app/
// ├── @modal/
// │   └── (..)photos/[id]/   // Wrong: (..) points to one level up
// │       └── page.js
// └── photos/
//     ├── page.js
//     └── [id]/
//         └── page.js
No interception occurs — clicking the link to /photos/1
navigates to the normal detail page instead of opening a modal.

When @modal and photos are at the same level (directly under app), use (.).

ok_example
// OK: @modal is directly under app, photos is also directly under app, so (.) is correct

// app/
// ├── @modal/
// │   └── (.)photos/[id]/    // Correct: (.) intercepts segments at the same level
// │       └── page.js
// └── photos/
//     ├── page.js
//     └── [id]/
//         └── page.js

If you find any errors or copyright issues, please .