Next.js Parallel Routes and Intercepting Routes
Advertisement
Next.js Parallel Routes and Intercepting Routes
Advanced routing patterns enable complex UIs like modals, sidebars, and multi-pane layouts without prop drilling or URL manipulation.
- Next.js Parallel Routes and Intercepting Routes
- What Are Parallel Routes?
- Creating Slots
- Slot Content
- Intercepting Routes
- Modal Example
- Real-World: Dashboard with Parallel Routes
- Intercepting Multiple Levels
- Unmatched Routes in Slots
- Conditional Rendering in Slots
- Real-World: Photo Gallery Modal
- Active Slot Detection
- FAQ
What Are Parallel Routes?
Parallel routes use the @ symbol to define slot segments that render simultaneously:
app/
├── layout.tsx → defines slots
├── page.tsx
├── @sidebar/
│ └── default.tsx
├── @modal/
│ └── default.tsx
Creating Slots
Define slots in the layout:
// app/layout.tsx
export default function Layout({
children,
sidebar,
modal,
}: {
children: React.ReactNode
sidebar: React.ReactNode
modal: React.ReactNode
}) {
return (
<div className="flex">
<aside className="w-64">{sidebar}</aside>
<main className="flex-1">{children}</main>
{modal}
</div>
)
}
The layout receives sidebar and modal as props.
Slot Content
Create default content for each slot:
// app/@sidebar/default.tsx
export default function SidebarDefault() {
return (
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
)
}
// app/@modal/default.tsx
export default function ModalDefault() {
return null // No modal by default
}
Intercepting Routes
Intercept routes to show content differently based on context:
app/
├── layout.tsx
├── (.)post/[id]/page.tsx → Intercepts /post/[id]
├── (.)post/[id]/modal.tsx
├── post/[id]/page.tsx → Default /post/[id]
The (.) prefix matches segments one level above.
Modal Example
Intercept a post route to show a modal when navigated from the feed:
// app/@modal/(.)post/[id]/page.tsx
'use client'
import { useRouter } from 'next/navigation'
export default function PostModal({ params }: { params: { id: string } }) {
const router = useRouter()
return (
<div className="modal-overlay" onClick={() => router.back()}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<button onClick={() => router.back()}>Close</button>
<h2>Post {params.id}</h2>
<p>Modal content rendered at app/@modal/(.)post/[id]/page.tsx</p>
</div>
</div>
)
}
// app/post/[id]/page.tsx
export default function PostPage({ params }: { params: { id: string } }) {
return (
<article>
<h1>Post {params.id}</h1>
<p>Full page view when accessing directly</p>
</article>
)
}
Real-World: Dashboard with Parallel Routes
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
notifications,
}: {
children: React.ReactNode
analytics: React.ReactNode
notifications: React.ReactNode
}) {
return (
<div className="grid grid-cols-4 gap-4">
<aside className="col-span-1">
<h3>Sidebar</h3>
{/* Sidebar content */}
</aside>
<main className="col-span-2">{children}</main>
<aside className="col-span-1 space-y-4">
{analytics}
{notifications}
</aside>
</div>
)
}
// app/dashboard/@analytics/page.tsx
export default function AnalyticsSlot() {
return (
<div className="bg-white p-4 rounded-lg">
<h3>Analytics</h3>
<p>Real-time analytics data</p>
</div>
)
}
// app/dashboard/@notifications/page.tsx
export default function NotificationsSlot() {
return (
<div className="bg-white p-4 rounded-lg">
<h3>Notifications</h3>
<ul>
<li>New message</li>
<li>System alert</li>
</ul>
</div>
)
}
Intercepting Multiple Levels
Different prefixes intercept different levels:
(.)— Same level(..)— One level up(..)(..)— Two levels up(...)— Root level
// app/(.)post/[id]/page.tsx
// Intercepts /post/[id] one level above
// app/(.)post/[id]/comments/(..)page.tsx
// Intercepts /post/[id]/comments two levels up from this route
Unmatched Routes in Slots
When a slot has no matching route, use default.tsx:
// app/@modal/default.tsx
export default function ModalDefault() {
return null
}
If users navigate to a route with no corresponding @modal segment, default.tsx renders instead.
Conditional Rendering in Slots
Show different content based on the current route:
// app/@modal/default.tsx
import { postIdFromUrl } from '@/lib/router'
export default function ModalSlot() {
const postId = postIdFromUrl()
if (!postId) {
return null
}
return (
<div className="modal">
<h2>Post {postId}</h2>
</div>
)
}
Real-World: Photo Gallery Modal
// app/layout.tsx
export default function RootLayout({
children,
gallery,
}: {
children: React.ReactNode
gallery: React.ReactNode
}) {
return (
<html>
<body>
{children}
{gallery}
</body>
</html>
)
}
// app/@gallery/(.)photo/[id]/page.tsx
'use client'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
export default function PhotoModal({ params }: { params: { id: string } }) {
const router = useRouter()
const photo = getPhotoById(params.id)
return (
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center"
onClick={() => router.back()}
>
<div className="bg-white rounded-lg max-w-2xl" onClick={(e) => e.stopPropagation()}>
<button
onClick={() => router.back()}
className="absolute top-2 right-2"
>
Close
</button>
<Image
src={photo.url}
alt={photo.title}
width={800}
height={600}
/>
<div className="p-4">
<h2>{photo.title}</h2>
<p>{photo.description}</p>
</div>
</div>
</div>
)
}
// app/photo/[id]/page.tsx
export default function PhotoPage({ params }: { params: { id: string } }) {
const photo = getPhotoById(params.id)
return (
<article>
<h1>{photo.title}</h1>
<Image src={photo.url} alt={photo.title} width={800} height={600} />
<p>{photo.description}</p>
</article>
)
}
Active Slot Detection
Determine if a modal is open:
// app/@modal/default.tsx
'use client'
import { usePathname } from 'next/navigation'
export default function ModalSlot() {
const pathname = usePathname()
const isOpen = pathname.includes('/modal') || pathname.includes('/post/')
if (!isOpen) {
return null
}
return <div>Modal content</div>
}
FAQ
Q: What's the difference between parallel routes and intercepting routes? A: Parallel routes render multiple segments simultaneously. Intercepting routes re-render a URL differently based on navigation context.
Q: Can I nest parallel routes? A: Yes, you can create complex hierarchies with multiple levels of parallel routes.
Q: How do I pass data between slots? A: Use Server Components and props. Each slot is independent, so prefer context or URL state for sharing.
Master parallel and intercepting routes for sophisticated layouts and modals.
Advertisement