Route Groups — (folder)

Groups routes under a shared folder and layout without adding a segment to the URL.

Signature
app/(marketing)/about/page.js   ->  /about
app/(shop)/cart/page.js         ->  /cart
app/(marketing)/layout.js       ->  wraps only routes inside (marketing)

Parameters

ParameterTypeDescription
$(folderName)folderA folder whose name is wrapped in parentheses. It is organizational only and is not included in the route's URL path, but layout.js, loading.js and error.js inside it still apply to every route it contains.

Errors

Routes in different groups that resolve to the same URL path — for example (marketing)/about/page.js and (shop)/about/page.js both resolving to /about — cause an error.

A folder wrapped in parentheses is invisible to the router. app/(marketing)/about/page.js serves /about, not /marketing/about. What the folder does keep is everything else a segment provides: a layout.js inside it wraps only the routes it contains, and so do loading.js and error.js.

When to reach for it

  • Splitting a growing app directory by team, concern or feature.
  • Giving a marketing site and a logged-in app separate root layouts.
  • Opting a few sibling routes into a shared layout while leaving the rest out — the reason most people find the convention.

What trips people up

Because the group name is stripped, two groups can silently collide: (marketing)/about and (shop)/about both resolve to /about and error.

Multiple root layouts are not free. Navigating between routes under different root layouts triggers a full page reload rather than a client-side transition. And if you drop the top-level app/layout.js entirely, the home route must live inside one of the groups, as app/(marketing)/page.js.

Example

Example · typescript
// app/(marketing)/layout.tsx
// Wraps /about and /blog, but not /cart.
export default function MarketingLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <>
      <SiteHeader />
      {children}
      <SiteFooter />
    </>
  )
}

// app/
// ├── (marketing)/
// │   ├── layout.tsx     wraps the two routes below
// │   ├── about/page.tsx  ->  /about
// │   └── blog/page.tsx   ->  /blog
// └── (shop)/
//     └── cart/page.tsx   ->  /cart   (no marketing layout)

When to use it

  • Keep marketing pages and dashboard pages in separate folders that share one URL space.
  • Wrap the checkout flow in its own layout without adding /checkout to every URL.
  • Give a public site and an authenticated app different <html> shells.
  • Group routes by owning team so a large app directory stays navigable.

Frequently asked questions

Does a folder in parentheses show up in the Next.js URL?

No. The parenthesised name is organizational and is excluded from the route's URL path, so app/(marketing)/about/page.js serves /about.

Can I have two route groups with the same page path?

No. Routes in different groups must not resolve to the same URL path — (marketing)/about/page.js and (shop)/about/page.js both resolve to /about and cause an error.

Why does navigation feel slow between my route groups?

If the groups have different root layouts, navigating between them triggers a full page reload rather than a client-side transition. This only applies to multiple root layouts, not to ordinary nested layouts inside groups.

More examples

Two root layouts, one application

With no app/layout.tsx above them, each group owns its own <html> and <body>. The cost is visible to users: navigating from /cart to /blog crosses a root layout boundary and triggers a full page reload instead of a client-side transition. Note also that the home route must now live in one of the groups, as app/(marketing)/page.tsx, or / has no page.

Example · typescript
// app/(marketing)/layout.tsx
export default function MarketingRootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className="marketing">{children}</body>
    </html>
  )
}

// app/(shop)/layout.tsx
export default function ShopRootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className="shop">{children}</body>
    </html>
  )
}

Opting a subset of siblings into a layout

Without the group, giving /cart and /payment a stripped-down layout would mean nesting them under a real /checkout segment and changing both URLs. The group buys the shared layout at zero URL cost: /cart and /payment keep their paths while /products and /account are untouched.

Example · typescript
// app/
// ├── layout.tsx                 root layout, applies to everything
// ├── (checkout)/
// │   ├── layout.tsx             minimal chrome, no nav
// │   ├── cart/page.tsx      ->  /cart
// │   └── payment/page.tsx   ->  /payment
// ├── products/page.tsx      ->  /products   (root layout only)
// └── account/page.tsx       ->  /account    (root layout only)

// app/(checkout)/layout.tsx
export default function CheckoutLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <main className="focused">{children}</main>
}

The collision that fails the build

Because the parenthesised name is stripped from the URL, the two files are indistinguishable to the router. This surfaces most often during a refactor when someone moves an existing route into a new group and forgets the old copy — the error names the duplicated path, not the group, so search for the segment rather than the folder.

Example · typescript
// Both of these resolve to /about — this is an error.
// app/(marketing)/about/page.tsx
// app/(shop)/about/page.tsx

// Fix by making the paths distinct:
// app/(marketing)/about/page.tsx    ->  /about
// app/(shop)/about-us/page.tsx      ->  /about-us

Discussion

  • Be the first to comment on this lesson.