We use cookies to enhance your experience on the site
CodeWorlds

Advanced Routing in Next.js 15 - Parallel Routes, Intercepting Routes, and Route Groups

In the Quantum Metropolis, the advanced transport system allows simultaneous travel along multiple parallel paths, in-flight traffic interception and redirection, and logical route grouping without affecting their addresses. Similarly, Next.js 15 offers advanced routing techniques that enable creating complex user interfaces with elegance and efficiency.

In this chapter, you will learn three powerful routing patterns in the App Router:

  1. Parallel Routes - rendering multiple routes simultaneously
  2. Intercepting Routes - intercepting navigation and showing alternative views
  3. Route Groups - organizing routes without affecting the URL

Parallel Routes - Simultaneous Route Rendering

What Are Parallel Routes?

Parallel Routes allow simultaneous rendering of multiple pages (or parts of pages) in the same layout. It's like holographic displays in the Quantum Metropolis that can show multiple independent information streams simultaneously.

Naming Convention - @folder

Parallel Routes use a special naming convention with the

@
prefix:

1app/
2├── layout.tsx
3├── page.tsx
4├── @analytics/       # Slot "analytics"
5│   └── page.tsx
6├── @team/           # Slot "team"
7│   └── page.tsx
8└── @activity/       # "activity" Slot
9    └── page.tsx

Basic Example - Dashboard with Multiple Panels

Imagine a Quantum Metropolis control panel that simultaneously displays analytics, team activity, and recent events:

1// app/dashboard/layout.tsx
2export default function DashboardLayout({
3  children,
4  analytics,
5  team,
6  activity,
7}: {
8  children: React.ReactNode;
9  analytics: React.ReactNode;
10  team: React.ReactNode;
11  activity: React.ReactNode;
12}) {
13  return (
14    <div className="quantum-dashboard">
15      <div className="main-content">
16        {children}
17      </div>
18
19      <div className="grid grid-cols-3 gap-6 mt-6">
20        <div className="panel">
21          <h2>Quantum Analytics</h2>
22          {analytics}
23        </div>
24
25        <div className="panel">
26          <h2>Team</h2>
27          {team}
28        </div>
29
30        <div className="panel">
31          <h2>Recent Activity</h2>
32          {activity}
33        </div>
34      </div>
35    </div>
36  );
37}
1// app/dashboard/@analytics/page.tsx
2async function getAnalytics() {
3  const res = await fetch('https://api.quantum.com/analytics', {
4    next: { revalidate: 60 }
5  });
6  return res.json();
7}
8
9export default async function Analytics() {
10  const data = await getAnalytics();
11
12  return (
13    <div className="analytics-panel">
14      <div className="stat">
15        <span className="label">Quantum Energy</span>
16        <span className="value">{data.energy}%</span>
17      </div>
18      <div className="stat">
19        <span className="label">System Efficiency</span>
20        <span className="value">{data.efficiency}%</span>
21      </div>
22    </div>
23  );
24}
1// app/dashboard/@team/page.tsx
2async function getTeam() {
3  const res = await fetch('https://api.quantum.com/team', {
4    next: { revalidate: 300 }
5  });
6  return res.json();
7}
8
9export default async function Team() {
10  const members = await getTeam();
11
12  return (
13    <ul className="team-list">
14      {members.map(member => (
15        <li key={member.id} className="team-member">
16          <img src={member.avatar} alt={member.name} />
17          <span>{member.name}</span>
18          <span className="status">{member.status}</span>
19        </li>
20      ))}
21    </ul>
22  );
23}
1// app/dashboard/@activity/page.tsx
2async function getActivity() {
3  const res = await fetch('https://api.quantum.com/activity', {
4    cache: 'no-store' // Always fresh data
5  });
6  return res.json();
7}
8
9export default async function Activity() {
10  const activities = await getActivity();
11
12  return (
13    <div className="activity-feed">
14      {activities.map(activity => (
15        <div key={activity.id} className="activity-item">
16          <span className="timestamp">{activity.time}</span>
17          <p>{activity.description}</p>
18        </div>
19      ))}
20    </div>
21  );
22}

Advantages of Parallel Routes

  1. Independent loading - each slot can be loaded independently with its own loading state
  2. Independent errors - an error in one slot doesn't affect others
  3. Selective navigation - you can navigate within one slot without affecting others
  4. Conditional rendering - you can conditionally show/hide slots

Default.tsx - Handling Missing Slots

When a user navigates in a way that doesn't match the slot structure, Next.js uses the

default.tsx
file:

1// app/dashboard/@analytics/default.tsx
2export default function DefaultAnalytics() {
3  return <div>No analytics data available</div>;
4}

Conditional Slot Rendering

1// app/dashboard/layout.tsx
2export default async function DashboardLayout({
3  children,
4  analytics,
5  team,
6  activity,
7  params,
8}: {
9  children: React.ReactNode;
10  analytics: React.ReactNode;
11  team: React.ReactNode;
12  activity: React.ReactNode;
13  params: Promise<{ userRole: string }>;
14}) {
15  const isAdmin = (await params).userRole === 'admin';
16
17  return (
18    <div className="quantum-dashboard">
19      <div className="main-content">{children}</div>
20
21      <div className="panels">
22        {analytics}
23        {team}
24        {isAdmin && activity} {/* Only for admins */}
25      </div>
26    </div>
27  );
28}

Intercepting Routes - Navigation Interception

What Are Intercepting Routes?

Intercepting Routes allow intercepting navigation and displaying an alternative view while the URL changes. It's like quantum tunnels in the Quantum Metropolis - they enable shortcuts without changing the final destination.

Naming Convention

Intercepting Routes use special prefixes:

  • (.)
    - intercepts segments at the same level
  • (..)
    - intercepts segments one level up
  • (..)(..)
    - intercepts segments two levels up
  • (...)
    - intercepts segments from the
    app/
    root

Example 1: Image Modal

A classic use case - a photo gallery where clicking opens a modal but the URL changes:

1app/
2├── layout.tsx
3├── page.tsx              # Galeria /
4├── photos/
5│   └── [id]/
6│       └── page.tsx      # Full photo page /photos/123
7└── @modal/
8    └── (..)photos/
9        └── [id]/
10            └── page.tsx  # Intercepting modal /photos/123
1// app/page.tsx - Galeria
2import Link from 'next/link';
3import Image from 'next/image';
4
5const photos = [
6  { id: '1', url: '/photos/quantum-city-1.jpg', title: 'Quantum District' },
7  { id: '2', url: '/photos/quantum-city-2.jpg', title: 'Central Plaza' },
8  { id: '3', url: '/photos/quantum-city-3.jpg', title: 'Night Skyline' },
9];
10
11export default function Gallery() {
12  return (
13    <div className="gallery-grid">
14      {photos.map(photo => (
15        <Link key={photo.id} href={`/photos/${photo.id}`}>
16          <div className="photo-card">
17            <Image
18              src={photo.url}
19              alt={photo.title}
20              width={300}
21              height={200}
22            />
23            <p>{photo.title}</p>
24          </div>
25        </Link>
26      ))}
27    </div>
28  );
29}
1// app/photos/[id]/page.tsx - Full photo page
2import Image from 'next/image';
3
4async function getPhoto(id: string) {
5  // Fetch photo data
6  return {
7    id,
8    url: `/photos/quantum-city-${id}.jpg`,
9    title: 'Quantum City',
10    description: 'View of Quantum Metropolis',
11  };
12}
13
14export default async function PhotoPage({
15  params,
16}: {
17  params: Promise<{ id: string }>;
18}) {
19  const photo = await getPhoto((await params).id);
20
21  return (
22    <div className="photo-full-page">
23      <h1>{photo.title}</h1>
24      <Image
25        src={photo.url}
26        alt={photo.title}
27        width={1200}
28        height={800}
29      />
30      <p>{photo.description}</p>
31    </div>
32  );
33}
1// app/@modal/(.)photos/[id]/page.tsx - Modal
2'use client';
3
4import { useRouter } from 'next/navigation';
5import Image from 'next/image';
6import { useEffect, useState } from 'react';
7
8export default function PhotoModal({
9  params,
10}: {
11  params: { id: string };
12}) {
13  const router = useRouter();
14  const [photo, setPhoto] = useState(null);
15
16  useEffect(() => {
17    // Fetch photo data
18    fetch(`/api/photos/${params.id}`)
19      .then(res => res.json())
20      .then(setPhoto);
21  }, [params.id]);
22
23  if (!photo) return null;
24
25  return (
26    <div
27      className="modal-overlay"
28      onClick={() => router.back()}
29    >
30      <div
31        className="modal-content"
32        onClick={(e) => e.stopPropagation()}
33      >
34        <button
35          className="modal-close"
36          onClick={() => router.back()}
37        >
3839        </button>
40
41        <Image
42          src={photo.url}
43          alt={photo.title}
44          width={800}
45          height={600}
46        />
47
48        <h2>{photo.title}</h2>
49        <p>{photo.description}</p>
50      </div>
51    </div>
52  );
53}
1// app/layout.tsx - Layout with modal
2export default function RootLayout({
3  children,
4  modal,
5}: {
6  children: React.ReactNode;
7  modal: React.ReactNode;
8}) {
9  return (
10    <html lang="pl">
11      <body>
12        {children}
13        {modal}
14      </body>
15    </html>
16  );
17}

Example 2: Login Modal with Intercepting Route

1app/
2├── layout.tsx
3├── page.tsx
4├── login/
5│   └── page.tsx          # Full login page /login
6└── @auth/
7    └── (.)login/
8        └── page.tsx      # Login modal
1// app/@auth/(.)login/page.tsx
2'use client';
3
4import { useRouter } from 'next/navigation';
5import { useState } from 'react';
6
7export default function LoginModal() {
8  const router = useRouter();
9  const [email, setEmail] = useState('');
10  const [password, setPassword] = useState('');
11
12  const handleSubmit = async (e: React.FormEvent) => {
13    e.preventDefault();
14    // Login logic
15    await loginUser(email, password);
16    router.back();
17  };
18
19  return (
20    <div className="modal-overlay" onClick={() => router.back()}>
21      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
22        <h2>Log in to Quantum Metropolis</h2>
23
24        <form onSubmit={handleSubmit}>
25          <input
26            type="email"
27            value={email}
28            onChange={(e) => setEmail(e.target.value)}
29            placeholder="Email"
30            required
31          />
32
33          <input
34            type="password"
35            value={password}
36            onChange={(e) => setPassword(e.target.value)}
37            placeholder="Password"
38            required
39          />
40
41          <button type="submit">Log in</button>
42        </form>
43      </div>
44    </div>
45  );
46}

Advantages of Intercepting Routes

  1. Better UX - quick modals without full page reload
  2. Shareable URLs - the URL changes, so the link can be shared
  3. Graceful fallback - direct URL access shows the full page
  4. Context preservation - the background remains visible behind the modal

Route Groups - Route Organization

What Are Route Groups?

Route Groups allow logical grouping of routes without affecting the URL structure. It's like districts in the Quantum Metropolis - they organize the city but don't affect building addresses.

Naming Convention - (folder)

Route Groups use parentheses in folder names:

1app/
2├── (marketing)/        # Group - doesn't affect the URL
3│   ├── layout.tsx      # Marketing layout
4│   ├── about/
5│   │   └── page.tsx    # URL: /about
6│   └── contact/
7│       └── page.tsx    # URL: /contact
8├── (shop)/            # Group - doesn't affect the URL
9│   ├── layout.tsx      # Shop layout
10│   ├── products/
11│   │   └── page.tsx    # URL: /products
12│   └── cart/
13│       └── page.tsx    # URL: /cart
14└── (dashboard)/       # Group - doesn't affect the URL
15    ├── layout.tsx      # Dashboard layout
16    ├── analytics/
17    │   └── page.tsx    # URL: /analytics
18    └── settings/
19        └── page.tsx    # URL: /settings

Example 1: Different Layouts for Different Sections

1// app/(marketing)/layout.tsx
2import MarketingHeader from '@/components/MarketingHeader';
3import MarketingFooter from '@/components/MarketingFooter';
4
5export default function MarketingLayout({
6  children,
7}: {
8  children: React.ReactNode;
9}) {
10  return (
11    <div className="marketing-layout">
12      <MarketingHeader />
13      <main className="marketing-content">
14        {children}
15      </main>
16      <MarketingFooter />
17    </div>
18  );
19}
1// app/(dashboard)/layout.tsx
2import DashboardSidebar from '@/components/DashboardSidebar';
3import DashboardHeader from '@/components/DashboardHeader';
4
5export default function DashboardLayout({
6  children,
7}: {
8  children: React.ReactNode;
9}) {
10  return (
11    <div className="dashboard-layout">
12      <DashboardHeader />
13      <div className="dashboard-container">
14        <DashboardSidebar />
15        <main className="dashboard-content">
16          {children}
17        </main>
18      </div>
19    </div>
20  );
21}

Example 2: Organization by Functionality

1app/
2├── (auth)/
3│   ├── login/
4│   │   └── page.tsx     # /login
5│   ├── register/
6│   │   └── page.tsx     # /register
7│   └── forgot-password/
8│       └── page.tsx     # /forgot-password
9├── (public)/
10│   ├── about/
11│   │   └── page.tsx     # /about
12│   └── blog/
13│       └── [slug]/
14│           └── page.tsx # /blog/:slug
15└── (protected)/
16    ├── middleware.ts    # Middleware only for this group
17    ├── dashboard/
18    │   └── page.tsx     # /dashboard
19    └── profile/
20        └── page.tsx     # /profile

Example 3: Multiple Root Layouts

Route Groups allow creating multiple root layouts in the same application:

1app/
2├── (shop)/
3│   ├── layout.tsx       # Root layout for the shop
4│   └── products/
5│       └── page.tsx
6└── (blog)/
7    ├── layout.tsx       # Root layout for the blog
8    └── posts/
9        └── page.tsx
1// app/(shop)/layout.tsx
2export default function ShopRootLayout({
3  children,
4}: {
5  children: React.ReactNode;
6}) {
7  return (
8    <html lang="pl">
9      <body className="shop-theme">
10        {children}
11      </body>
12    </html>
13  );
14}
1// app/(blog)/layout.tsx
2export default function BlogRootLayout({
3  children,
4}: {
5  children: React.ReactNode;
6}) {
7  return (
8    <html lang="pl">
9      <body className="blog-theme">
10        {children}
11      </body>
12    </html>
13  );
14}

Advantages of Route Groups

  1. Code organization - logical grouping without affecting the URL
  2. Different layouts - ability to apply different layouts for different sections
  3. Better project structure - easier management of large applications
  4. Separation of concerns - different teams can work on different groups

Combining All Three Techniques

The true power of the App Router reveals itself when you combine all three techniques together:

1app/
2├── (marketing)/
3│   ├── layout.tsx
4│   ├── page.tsx
5│   └── @modal/
6│       └── (.)contact/
7│           └── page.tsx
8├── (dashboard)/
9│   ├── layout.tsx
10│   ├── @analytics/
11│   │   └── page.tsx
12│   ├── @team/
13│   │   └── page.tsx
14│   └── @activity/
15│       └── page.tsx
16└── (shop)/
17    ├── layout.tsx
18    ├── products/
19    │   └── [id]/
20    │       └── page.tsx
21    └── @cart/
22        └── (.)products/
23            └── [id]/
24                └── page.tsx

Example: E-commerce with Advanced Routing

1// app/(shop)/layout.tsx
2export default function ShopLayout({
3  children,
4  cart,
5}: {
6  children: React.ReactNode;
7  cart: React.ReactNode;
8}) {
9  return (
10    <div className="shop-layout">
11      <ShopHeader />
12      <main>{children}</main>
13      {cart}
14      <ShopFooter />
15    </div>
16  );
17}
1// app/(shop)/products/[id]/page.tsx
2async function getProduct(id: string) {
3  const res = await fetch(`https://api.quantum.com/products/${id}`);
4  return res.json();
5}
6
7export default async function ProductPage({
8  params,
9}: {
10  params: Promise<{ id: string }>;
11}) {
12  const product = await getProduct((await params).id);
13
14  return (
15    <div className="product-page">
16      <h1>{product.name}</h1>
17      <p>{product.description}</p>
18      <button>Add to cart</button>
19    </div>
20  );
21}
1// app/(shop)/@cart/(.)products/[id]/page.tsx
2'use client';
3
4import { useRouter } from 'next/navigation';
5
6export default function QuickViewModal({
7  params,
8}: {
9  params: { id: string };
10}) {
11  const router = useRouter();
12
13  return (
14    <div className="modal-overlay" onClick={() => router.back()}>
15      <div className="quick-view" onClick={(e) => e.stopPropagation()}>
16        <ProductQuickView productId={params.id} />
17        <button onClick={() => router.back()}>Close</button>
18      </div>
19    </div>
20  );
21}

Best Practices

1. Use Parallel Routes for Independent Sections

1// ✅ Good - independent panels
2app/dashboard/
3├── @analytics/
4├── @team/
5└── @notifications/
6
7// ❌ Bad - dependent components
8app/dashboard/
9├── @header/      // This should be a component, not a slot
10├── @footer/      // This should be a component, not a slot
11└── @content/

2. Use Intercepting Routes for Modals and Overlays

1// ✅ Good
2app/
3├── photos/[id]/page.tsx
4└── @modal/(.)photos/[id]/page.tsx
5
6// ❌ Bad - modal as a separate route
7app/
8├── photos/[id]/page.tsx
9└── modal/photos/[id]/page.tsx

3. Use Route Groups for Organization

1// ✅ Good - logical grouping
2app/
3├── (marketing)/
4├── (dashboard)/
5└── (auth)/
6
7// ❌ Bad - grouping by file type
8app/
9├── (pages)/
10├── (components)/
11└── (api)/

Summary

Advanced routing techniques in Next.js 15 are like the advanced transport network of the Quantum Metropolis - they enable creating complex, efficient, and elegant user interfaces:

  1. Parallel Routes - render multiple routes simultaneously for independent sections
  2. Intercepting Routes - intercept navigation for better UX with modals and overlays
  3. Route Groups - organize code without affecting the URL

These techniques give you incredible control over the application structure and user experience. Just as engineers of the Quantum Metropolis design multi-level transport systems for maximum efficiency, you too can design advanced routing structures for optimal UX.

The key to success is understanding when to use each technique and how to combine them to create a cohesive, efficient Next.js 15 application.

Go to CodeWorlds