React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

route-groupsorganizationlayoutsmultiple-root-layouts

Route Groups

Route groups use (folderName) parentheses to organize routes without affecting the URL structure. They enable multiple layouts at the same route level.

Recipe

Quick-reference recipe card — copy-paste ready.

app/
├── (marketing)/
│   ├── layout.tsx          # Marketing layout (no nav, full-width)
│   ├── page.tsx            # / (home page)
│   ├── about/page.tsx      # /about
│   └── pricing/page.tsx    # /pricing
├── (app)/
│   ├── layout.tsx          # App layout (sidebar, auth required)
│   ├── dashboard/page.tsx  # /dashboard
│   └── settings/page.tsx   # /settings
└── layout.tsx              # Root layout (shared by both groups)

Key rule: The parenthesized folder name is stripped from the URL. (marketing)/about/page.tsx serves /about, not /(marketing)/about.

Working Example

// app/layout.tsx — Root layout shared by all groups
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
// app/(marketing)/layout.tsx — Public marketing pages
import Link from "next/link";
 
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
  return (
    <div>
      <header className="flex items-center justify-between px-8 py-4">
        <Link href="/" className="text-xl font-bold">Brand</Link>
        <nav className="space-x-4">
          <Link href="/about">About</Link>
          <Link href="/pricing">Pricing</Link>
          <Link href="/dashboard">Sign In</Link>
        </nav>
      </header>
      <main>{children}</main>
    </div>
  );
}
// app/(marketing)/page.tsx — Home page at /
export default function HomePage() {
  return (
    <section className="py-20 text-center">
      <h1 className="text-5xl font-bold">Welcome to Our Product</h1>
      <p className="mt-4 text-lg text-gray-600">The best solution for your needs.</p>
    </section>
  );
}
// app/(app)/layout.tsx — Authenticated app pages
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import Link from "next/link";
 
export default async function AppLayout({ children }: { children: React.ReactNode }) {
  const session = await auth();
  if (!session) redirect("/login");
 
  return (
    <div className="flex min-h-screen">
      <aside className="w-64 border-r p-4">
        <nav className="space-y-2">
          <Link href="/dashboard" className="block">Dashboard</Link>
          <Link href="/settings" className="block">Settings</Link>
        </nav>
      </aside>
      <main className="flex-1 p-8">{children}</main>
    </div>
  );
}
// app/(app)/dashboard/page.tsx — Dashboard at /dashboard
export default function DashboardPage() {
  return <h1>Dashboard</h1>;
}

Deep Dive

How It Works

  • Parenthesized folders are URL-invisible. (marketing) never appears in the URL — it is purely an organizational mechanism.
  • Each group can have its own layout. This is the primary use case: different visual shells for different sections of the site.
  • Groups share the root layout. The root app/layout.tsx still wraps everything. Group layouts nest inside it.
  • You can have multiple root layouts by removing the top-level app/layout.tsx and placing a layout.tsx with <html> and <body> inside each group. Each group then has a completely independent document.
  • Route groups can be nested. (marketing)/(campaigns)/page.tsx is valid — both group segments are stripped.
  • Groups do not create route boundaries. A loading.tsx or error.tsx inside a group works the same as in any folder.
  • No conflicts between groups at the same path. You cannot have (marketing)/about/page.tsx AND (app)/about/page.tsx — both resolve to /about and Next.js will error.

Variations

# Multiple root layouts (completely separate HTML documents)
app/
├── (marketing)/
│   ├── layout.tsx     # Must include <html> and <body>
│   └── page.tsx       # /
├── (app)/
│   ├── layout.tsx     # Must include <html> and <body>
│   └── dashboard/
│       └── page.tsx   # /dashboard
# No top-level app/layout.tsx in this pattern
// Using groups for auth vs. public separation
// app/(auth)/layout.tsx
export default function AuthLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <div className="w-full max-w-md">{children}</div>
    </div>
  );
}
 
// app/(auth)/login/page.tsx → /login
// app/(auth)/register/page.tsx → /register
# Groups for feature organization (no layout difference)
app/
├── (features)/
│   ├── billing/page.tsx      # /billing
│   └── invoices/page.tsx     # /invoices
├── (admin)/
│   ├── layout.tsx            # Admin layout with guard
│   └── users/page.tsx        # /users

TypeScript Notes

// Route group layouts have the same types as any layout
interface GroupLayoutProps {
  children: React.ReactNode;
}
 
// No special types needed — groups are purely a file-system concept
// Params from dynamic segments above the group still flow through
// app/(app)/[orgId]/settings/page.tsx
interface SettingsPageProps {
  params: Promise<{ orgId: string }>;
}

Gotchas

  • Two groups cannot define the same route. If (a)/about/page.tsx and (b)/about/page.tsx both exist, the build fails.
  • The home page / can only live in one group. Place page.tsx in the group that should own the root URL.
  • Multiple root layouts means no shared layout. If each group has its own <html>, navigating between groups triggers a full page reload.
  • Nested groups compound. (a)/(b)/page.tsx is allowed, but both layers are stripped — the URL is just /.
  • loading.tsx and error.tsx in a group apply to all routes inside that group — they do not leak into other groups.
  • Middleware does not know about groups. Middleware matchers work on actual URL paths (without the parenthesized names).

Alternatives

ApproachWhen to Use
Nested folders without parensWhen the folder should appear in the URL
Parallel Routes (@slot)Rendering multiple views simultaneously in one layout
Middleware-based redirectsRouting users to different sections based on auth or role
Separate Next.js appsCompletely independent deployments for different sections

FAQs

Does the parenthesized folder name appear in the URL?

No. The parenthesized name is stripped entirely. (marketing)/about/page.tsx serves /about, not /(marketing)/about.

Can two route groups define a page at the same URL path?

No. If (a)/about/page.tsx and (b)/about/page.tsx both exist, the build fails because both resolve to /about.

Which route group should own the home page (/)?

Only one group can contain page.tsx at its root. Place it in whichever group should own the / URL (typically the marketing or public group).

What happens when you navigate between routes in different groups that have separate root layouts?

A full page reload occurs. When each group has its own <html> and <body> (multiple root layouts), navigating between groups cannot be a soft client-side transition.

Do loading.tsx and error.tsx in a route group leak into other groups?

No. They apply only to routes within that group. Each group's boundary files are scoped to its own routes.

Gotcha: Does middleware know about route group names?

No. Middleware matchers work on actual URL paths. The parenthesized folder names are invisible to middleware. Match on the real URL like /about, not /(marketing)/about.

How do you create multiple root layouts?

Remove the top-level app/layout.tsx and place a layout.tsx with <html> and <body> inside each group.

app/
├── (marketing)/
│   ├── layout.tsx   # Must include <html> and <body>
│   └── page.tsx
├── (app)/
│   ├── layout.tsx   # Must include <html> and <body>
│   └── dashboard/page.tsx
Can route groups be nested?

Yes. (a)/(b)/page.tsx is valid. Both group segments are stripped, so the URL is just /.

What TypeScript types do route group layouts use?

The same types as any layout. There are no special types for route groups since they are purely a file-system concept.

interface GroupLayoutProps {
  children: React.ReactNode;
}
How do you type a page inside a route group that also has a dynamic segment above it?
// app/(app)/[orgId]/settings/page.tsx
interface SettingsPageProps {
  params: Promise<{ orgId: string }>;
}

Params from dynamic segments above the group still flow through normally.

Gotcha: When using groups for auth separation, how do you protect the app group?

Check auth in the group's layout and redirect if unauthenticated.

// app/(app)/layout.tsx
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
 
export default async function AppLayout({
  children,
}: { children: React.ReactNode }) {
  const session = await auth();
  if (!session) redirect("/login");
  return <div>{children}</div>;
}
Are route groups useful even when you do not need different layouts?

Yes. They can be used purely for file-system organization, grouping related routes together without affecting the URL structure.