Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
18 minLesson 17 of 33
Next.js App Router

Route Groups & Parallel Routes

Route Groups & Parallel Routes

Two powerful App Router features that most tutorials skip: route groups for organizing routes without affecting URLs, and parallel routes for showing multiple pages simultaneously in one layout.

Route Groups

A folder wrapped in parentheses (name) creates a route group. The folder name is excluded from the URL path.

src/app/
├── (marketing)/          ← Group name is invisible in URLs
│   ├── layout.tsx        → Applies to / about/ pricing/ only
│   ├── page.tsx          → URL: /
│   ├── about/
│   │   └── page.tsx      → URL: /about
│   └── pricing/
│       └── page.tsx      → URL: /pricing
├── (auth)/               ← Another group
│   ├── layout.tsx        → Minimal layout for auth pages
│   ├── login/
│   │   └── page.tsx      → URL: /login
│   └── register/
│       └── page.tsx      → URL: /register
└── (dashboard)/          ← Protected section
    ├── layout.tsx        → Authenticated layout with sidebar
    ├── dashboard/
    │   └── page.tsx      → URL: /dashboard
    └── settings/
        └── page.tsx      → URL: /settings

The marketing pages get a public layout with a header and footer. Auth pages get a minimal layout — just the form, centered. Dashboard pages get a sidebar layout with an auth check. All share the same root layout.tsx.

Without route groups you'd have to put all these layouts inside nested folders, affecting the URL structure.

Different Layouts at the Same Level

Route groups let you apply different root layouts to different top-level sections:

// src/app/(marketing)/layout.tsx
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
    return (
        <>
            <MarketingNav />
            {children}
            <MarketingFooter />
        </>
    );
}

// src/app/(auth)/layout.tsx
export default function AuthLayout({ children }: { children: React.ReactNode }) {
    return (
        <div className="min-h-screen bg-gray-50 flex items-center justify-center">
            <div className="bg-white rounded-2xl shadow-lg p-8 w-full max-w-md">
                {children}
            </div>
        </div>
    );
}

// src/app/(dashboard)/layout.tsx
import { redirect } from "next/navigation";
import { getSession } from "@/lib/auth";

export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
    const user = await getSession();
    if (!user) redirect("/login");
    
    return (
        <div className="flex">
            <Sidebar user={user} />
            <main className="flex-1">{children}</main>
        </div>
    );
}

Parallel Routes

Parallel routes show multiple pages in the same layout simultaneously using named slots. A folder named @slotName defines a slot:

src/app/
├── layout.tsx
├── page.tsx
├── @modal/
│   ├── default.tsx       → Shown when no modal route matches (usually null)
│   └── (.)photos/[id]/
│       └── page.tsx      → Intercepted photo route rendered as a slot
└── photos/
    ├── page.tsx           → /photos (grid)
    └── [id]/
        └── page.tsx       → /photos/123 (full page)

The layout receives all slots as props:

// src/app/layout.tsx
export default function Layout({
    children,
    modal,
}: {
    children: React.ReactNode;
    modal: React.ReactNode;
}) {
    return (
        <html>
            <body>
                {children}
                {modal}   {/* Renders on top when a modal route is active */}
            </body>
        </html>
    );
}
// src/app/@modal/default.tsx — shown when no modal is active
export default function ModalDefault() {
    return null;
}

Intercepting Routes

The (.), (..), (..)(..) prefix intercepts navigation to another route and shows it in the current context:

(.)   → Intercept routes at the same level
(..)  → Intercept routes one level up
(..)(...) → Two levels up
(...)  → From the root
// src/app/@modal/(.)photos/[id]/page.tsx
// Intercepts navigation to /photos/[id] and shows it as a modal

import { Modal } from "@/components/Modal";
import PhotoDetail from "@/components/PhotoDetail";

export default async function PhotoModal({ params }: { params: { id: string } }) {
    const photo = await getPhoto(params.id);
    
    return (
        <Modal>
            <PhotoDetail photo={photo} />
        </Modal>
    );
}
// src/components/Modal.tsx
"use client";

import { useRouter } from "next/navigation";

export function Modal({ children }: { children: React.ReactNode }) {
    const router = useRouter();
    
    return (
        <div
            className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4"
            onClick={() => router.back()}
        >
            <div
                className="bg-white rounded-2xl max-w-2xl w-full max-h-[90vh] overflow-auto"
                onClick={e => e.stopPropagation()}
            >
                {children}
            </div>
        </div>
    );
}

Now:

  • Click a photo thumbnail → router.push("/photos/123") → Modal opens (intercepted)
  • Paste /photos/123 directly → Full-page photo view (not intercepted)
  • Press back → Modal closes, back on the grid
  • Refresh on /photos/123 → Full-page view loads

This is exactly how Instagram, Pinterest, and most image grid apps work. With Next.js, it's just a few files.

Practical Use Cases

Modal with URL — Any dialog that should be shareable/bookmarkable:

  • Image viewer on a gallery
  • Quick product preview on a shop
  • User profile popup in an app

Split view — Two panels with independent navigation:

  • Email client (inbox + message)
  • File explorer (folders + content)
  • Admin panel (list + detail)

Tab persistence — Tabs that each maintain their own URL and state:

src/app/
├── @inbox/
│   ├── default.tsx
│   └── page.tsx    → /inbox content
└── @notifications/
    ├── default.tsx
    └── page.tsx    → /notifications content

Both slots render simultaneously in the layout. Each has its own URL state. Refreshing the page restores both.

Next lesson: When to use Client Components — the decision framework for Server vs Client.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!