Next.js Routing: Intercepting Routes

6 min read
Cover image for Next.js Routing: Intercepting Routes

Next.js's app router introduces an advanced routing pattern: intercepting routes. Lets jump into this routing type:

What are the Intercepting Routes?

Sometimes, you may want to give users quick access to a page by opening it in a modal without fully navigating away from the current page. At other times, you might not want to show the the target page at all but still you may want to avoid changing the route, page structures or you may want to avoid extra work. Next.js has a solution, it is intercepting routes.

Intercepting routes allow you to display content from another page, like a modal, without navigating away from the current view. In this case, the intercepted page still exists, and refreshing the intercepted route will display the content directly. Sharing the link will also give direct access to that page.

How Intercepting Routes Work?

Intercepting routes in Next.js are based on route segments, not the file system. We can use the following conventions for route matching:

(.)  // Matches segments at the same level.
(..) // Matches segments from one level up.
(...) // Matches segments from the root app directory.

Let’s say you want to intercept the

photo
segment from within the
feed
segment, and show something else to the user. You can define the route as:

/feed/(..)photo/page.tsx

And you can put whatever you want to display on the

page.tsx
. Don't forget: the intercepted root is still accessible by a refresh or link sharing.

Still Not Working?: After making changes to intercepting routes, you may not see the changes instantly, you can restart the server, do a hard refresh, or delete the

.next
folder to see the changes.

Using Dynamic Routes with Interception

You can also create dynamic intercepting routes like this and intercept them:

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

Combining Parallel Routes and Intercepting Routes

Let's take another example using parallel routes. I'll cover parallel routes in more detail in another article, but for now, let’s combine them with intercepting routes.

Imagine we have two segments:

feed
(an image gallery) and
photo
(photo details). Clicking on an image in the feed opens a modal. Here’s what the route structure looks like:

/feed/@modal/(..)photo/[id]/page.tsx

Here, the

@modal
folder is a named slot used to create parallel routes, not an actual route segment. The
(..)photo/[id]
route intercepts and opens the modal from the
feed
page, without changing the full page. In
page.tsx
, we define the modal component. Additionally, in
app/layout.tsx
, we need to include the modal so it can be rendered properly across the app.

To ensure proper fallback behavior, in the

(..)photo
segment, we should add a
default.tsx
file. This file will return
null
when nothing is intercepted, preventing any unwanted content from being displayed when the route isn't active.

Let's add a modal for our page:

import { useRouter } from 'next/router';
import { useEffect } from 'react';

const PhotoModal = () => {
  const router = useRouter();
  const { id } = router.query;

  useEffect(() => {
    const handleEscape = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        router.back();
      }
    };

    window.addEventListener('keydown', handleEscape);
    return () => window.removeEventListener('keydown', handleEscape);
  }, [router]);

  const closeModal = () => {
    router.back();
  };

  return (
    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
      <div className="bg-white p-6 rounded-lg shadow-lg relative">
        <button 
          onClick={closeModal} 
          className="absolute top-2 right-2 text-gray-500 hover:text-gray-800"
        >
          &times;
        </button>
        <h1 className="text-xl font-semibold mb-4">Photo ID: {id}</h1>
      </div>
    </div>
  );
};

export default PhotoModal;

Here how we handle the modal:


// layout.tsx
import { ReactNode } from 'react';

interface LayoutProps {
  children: ReactNode;
  modal: ReactNode;
}

const Layout = ({ children, modal }: LayoutProps) => {
  return (
    <div>
      <main>{children}</main>
      {modal}
    </div>
  );
};

export default Layout;

//default.tsx
const Default = () => {
  return null;
};

export default Default;

Why Use Intercepting Routes?

Using intercepting routes with modals provides solutions to several common challenges that comes with modals:

  • Shareable URLs: The modal's content is accessible via a unique URL, making it easy to share with others while preserving the current page state.

  • Context Retention on Refresh: Even if the page is refreshed, the modal stays open, ensuring that users don’t lose their place or context.

  • Navigation: When users navigate back, the modal automatically closes, returning them to the previous page instead of causing a full redirect. The modal reopens when navigating forward, maintaining a smooth user experience.

*https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes

Ezgi Ergün - Front-end Developer @ Startbase

Latest Blog Posts

Cover image for WWDC24 - Apple Intelligence
WWDC24 - Apple Intelligence
Apple Intelligence is a personal intelligence system that brings powerful generative models right to the core of your iPhone, iPad, and Mac.
September 12, 2024
Cover image for Next.js Routing: Intercepting Routes
Next.js Routing: Intercepting Routes
Next.js intercepting routes allows developers to modify or block navigation based on conditions, making it ideal for managing complex logic like authentication or loading states before accessing specific pages.
September 13, 2024