AiTechWorlds
AiTechWorlds
A route group — a folder wrapped in parentheses like (marketing) — organizes routes in the file system without adding a segment to the URL.
Think of it as a filing cabinet's internal folder labels: you can sort "Invoices" and "Contracts" into separate drawers for your own sanity, but a client asking for a specific document never sees or types the drawer name — only the document itself. Route groups work the same way for URLs, and pair with parallel routes — a second App Router feature that shows multiple pages simultaneously in one layout — to cover the navigation patterns most tutorials skip.
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: /settingsThe 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.
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 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;
}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:
router.push("/photos/123") → Modal opens (intercepted)/photos/123 directly → Full-page photo view (not intercepted)/photos/123 → Full-page view loadsThis is exactly how Instagram, Pinterest, and most image grid apps work. With Next.js, it's just a few files.
Modal with URL — Any dialog that should be shareable/bookmarkable:
Split view — Two panels with independent navigation:
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 contentBoth 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