Skip to main content

Next.js Intercepting Routes

Intercepting routes is one of the more powerful advanced routing features in Next.js. It allows you to "intercept" the default routing behavior to show a different UI while preserving the context of the current page. This technique is particularly useful for creating modals, slideovers, and other UI elements that need to "overlay" the current view rather than navigating away from it.

Introduction to Intercepting Routes

In traditional web applications, clicking on a link typically navigates the user to a completely new page. However, modern web applications often need more fluid interactions, such as:

  • Opening an image in a modal while still seeing the gallery in the background
  • Displaying a login form without leaving the current page
  • Showing a preview of an item while browsing a list

Intercepting routes in Next.js enables these experiences while maintaining proper URL-based navigation, history, and shareable links.

How Intercepting Routes Work

The key concept behind intercepting routes is the ability to "catch" a navigation that would normally load a new page and instead render a different component while preserving the URL in the browser's address bar.

Intercepting Route Convention

Next.js uses a special file naming convention for intercepting routes. The syntax uses parentheses with dots to define how interception should work:

  • (.) - Intercept segments in the same level
  • (..) - Intercept segments from one level above
  • (..)(..) - Intercept segments from two levels above
  • (...) - Intercept segments from the root app directory

Basic Syntax and Examples

Let's look at a basic example of intercepting routes to create a photo modal:

File Structure Example

app/
├── gallery/
│ ├── page.js # Gallery page with list of photos
│ ├── [id]/
│ │ ├── page.js # Full photo page
│ │ └── (..)[id]/
│ │ └── page.js # Modal photo view (intercepts the route)

Implementation

First, let's create the gallery page that displays a list of photos:

jsx
// app/gallery/page.js
import Link from 'next/link';
import { getPhotos } from '@/lib/photos';

export default async function GalleryPage() {
const photos = await getPhotos();

return (
<div className="grid grid-cols-3 gap-4">
<h1 className="col-span-3 text-2xl font-bold">Photo Gallery</h1>

{photos.map((photo) => (
<Link
key={photo.id}
href={`/gallery/${photo.id}`}
className="hover:opacity-80"
>
<img
src={photo.thumbnailUrl}
alt={photo.title}
className="rounded-lg"
/>
</Link>
))}
</div>
);
}

Next, let's create the dedicated photo page that users would see if they navigate directly to a photo:

jsx
// app/gallery/[id]/page.js
import { getPhoto } from '@/lib/photos';
import Link from 'next/link';

export default async function PhotoPage({ params }) {
const photo = await getPhoto(params.id);

return (
<div className="container mx-auto my-10">
<Link href="/gallery" className="text-blue-500 mb-4 block">
← Back to gallery
</Link>

<h1 className="text-2xl font-bold mb-4">{photo.title}</h1>

<div className="w-full">
<img
src={photo.url}
alt={photo.title}
className="rounded-lg max-h-[70vh] mx-auto"
/>
</div>

<div className="mt-6">
<h2 className="text-xl font-semibold">Photo Details</h2>
<p>Taken by: {photo.photographer}</p>
<p>Date: {photo.dateTaken}</p>
</div>
</div>
);
}

Finally, let's create the intercepting route for the modal view:

jsx
// app/gallery/(..)[id]/page.js
import { getPhoto } from '@/lib/photos';
import Link from 'next/link';
import { Dialog } from '@/components/Dialog';

export default async function PhotoModal({ params }) {
const photo = await getPhoto(params.id);

return (
<Dialog>
<div className="bg-white rounded-lg p-6 max-w-3xl w-full mx-auto">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">{photo.title}</h2>
<Link href="/gallery" className="text-gray-500">

</Link>
</div>

<div className="w-full">
<img
src={photo.url}
alt={photo.title}
className="rounded-lg max-h-[60vh] mx-auto"
/>
</div>

<div className="mt-4 text-sm text-gray-600">
<p>Taken by: {photo.photographer}</p>
</div>
</div>
</Dialog>
);
}

And let's create a simple Dialog component for the modal:

jsx
// components/Dialog.jsx
'use client';

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

export function Dialog({ children }) {
const router = useRouter();

useEffect(() => {
// Lock the scroll when modal opens
document.body.classList.add('overflow-hidden');

// Clean up function
return () => {
document.body.classList.remove('overflow-hidden');
};
}, []);

return (
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50"
onClick={() => router.back()}
>
<div onClick={(e) => e.stopPropagation()}>
{children}
</div>
</div>
);
}

Practical Use Cases for Intercepting Routes

1. Authentication Modal

You can create an authentication system where the login form appears as a modal without disrupting the user's current context:

app/
├── login/
│ ├── page.js # Dedicated login page
├── dashboard/
│ ├── page.js # Dashboard content
│ ├── (..login)/
│ │ └── page.js # Login modal that intercepts within dashboard

2. Shopping Cart Preview

When a user clicks to view their cart, you can show a slideover instead of a full page:

app/
├── products/
│ ├── page.js # Products listing
├── cart/
│ ├── page.js # Full cart page
├── (cart)/
│ └── page.js # Cart slideover that intercepts from root

3. Quick View Product Details

app/
├── products/
│ ├── page.js # Products listing
│ ├── [id]/
│ │ ├── page.js # Full product detail page
│ │ └── (..)[id]/
│ │ └── page.js # Quick view modal

Advanced Patterns

Conditional Interception

Sometimes you want to intercept the route only under certain conditions. You can use client components and the useRouter hook to implement this:

jsx
'use client';

import { useRouter, usePathname } from 'next/navigation';
import { useEffect } from 'react';

export default function InterceptionHandler({ children }) {
const router = useRouter();
const pathname = usePathname();
const isModal = pathname.includes('/modal/');

useEffect(() => {
// Check if this should be shown as a modal based on referrer or state
const shouldShowAsModal = window.history.state?.modal === true;

if (isModal && !shouldShowAsModal) {
// If directly navigated to the modal URL, redirect to the full page version
router.replace(pathname.replace('/modal/', '/'));
}
}, [isModal, pathname, router]);

return children;
}

Parallel Routes with Interception

You can combine intercepting routes with parallel routes for even more advanced UI patterns:

app/
├── products/
│ ├── page.js
│ ├── @modal/
│ │ └── default.js # Default (empty) state for the modal slot
│ ├── [id]/
│ │ ├── page.js # Product page
│ │ └── @modal/
│ │ └── (..)cart/
│ │ └── page.js # Cart modal that shows in the modal slot

Common Issues and Solutions

1. Modal closing on navigation

When using modals with intercepting routes, you might notice that if a user navigates to another page and then comes back, the modal is closed. To preserve the modal state:

jsx
// Use state to track if the modal should be shown
const [showModal, setShowModal] = useState(false);

// When linking to a modal, add state information
<Link
href={`/gallery/${photo.id}`}
onClick={() => {
// Add a custom state object to the history entry
window.history.pushState({ modal: true }, '');
}}
>
View Photo
</Link>

2. SEO considerations

Remember that search engines will see the intercepted route as its full page version. Make sure both the modal/overlay view and the full page view provide good experiences:

jsx
// In your intercepted route, add proper metadata
export const metadata = {
title: 'Photo Details - View in Gallery Context',
description: 'View photo details while browsing the gallery',
}

Summary

Intercepting routes in Next.js provides a powerful way to create modern UI patterns like modals and slideovers while maintaining proper URL-based navigation. This feature enables:

  • Creating overlay UIs without disrupting the current page context
  • Maintaining shareable URLs that work both as standalone pages and as overlays
  • Building more fluid, app-like experiences in your Next.js applications

By using the special file naming conventions (.), (..), and (...), you can control which routes get intercepted and how they behave, creating sophisticated navigation patterns for your users.

Additional Resources and Exercises

Resources

Exercises

  1. Basic Modal: Create a simple photo gallery with modal views using intercepting routes.

  2. Multi-level Interception: Build a nested UI where both a category page and a product page can be intercepted to show as overlays.

  3. Conditional Interception: Create a system where routes are only intercepted if the user navigates from certain pages, but show as full pages when accessed directly.

  4. Combined Features: Build a mini e-commerce site that uses intercepting routes for quick views, parallel routes for shopping cart, and server components for product data.

By mastering intercepting routes, you'll be able to create seamless, app-like experiences in your Next.js applications while maintaining the benefits of URL-based navigation.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)