How to Structure Next.js 14 App Router Projects Using Route Groups

Route groups allow you to organize routes into logical folders—such as (admin) or (website)—without exposing the folder names in the URL, enabling shared layouts and server-side logic per section while keeping URLs clean.

The Next.js 14 App Router introduces a powerful convention for scaling complex applications through route groups. In the woosal1337/blog repository, this pattern separates public-facing content from administrative interfaces using parentheses-wrapped folders like (website) and (admin). Understanding how to implement these groups is essential for maintaining semantic URLs while keeping your file system organized.

What Are Route Groups?

Route groups are folders in the app/ directory whose names are wrapped in parentheses, e.g., (marketing) or (shop). When Next.js builds the application, it flattens the folder tree and strips out any parentheses-wrapped segments, meaning the group name has zero impact on the final URL structure.

Key characteristics of route groups:

  • URL Neutrality: A page inside app/(admin)/dashboard/page.tsx is accessible at /dashboard, not /admin/dashboard.
  • Layout Isolation: You can place a layout.tsx inside a route group to share UI or data fetching logic across all routes in that group without affecting the root layout.
  • Logical Colocation: Group related routes (blog posts, user settings, API endpoints) together in the file system for better maintainability while preserving flat URL hierarchies.

Project Structure with Route Groups

The woosal1337/blog repository demonstrates a canonical structure that separates concerns using route groups. This organization keeps authentication logic, styling, and components isolated to their specific domains.


app/
│─ layout.tsx
│─ page.tsx
│─ (website)/
│   │─ layout.tsx
│   │─ about/
│   │   └─ page.tsx
│   │─ blog/
│   │   ├─ layout.tsx
│   │   ├─ page.tsx
│   │   └─ [slug]/
│   │       └─ page.tsx
│─ (admin)/
│   │─ layout.tsx
│   │─ dashboard/
│   │   └─ page.tsx
│   └─ users/
│       └─ [id]/
│           └─ page.tsx
│─ (api)/
│   └─ hello/
│       └─ route.ts

  • app/layout.tsx: The root layout provides the global HTML skeleton and providers.
  • (website): Contains all public-facing pages and components like the navigation bar.
  • (admin): Houses administrative tools with a dedicated layout that enforces server-side authentication.
  • (api): Groups API routes separately from UI pages to avoid accidental route collisions.

Implementing Layouts Inside Route Groups

Each route group can define its own layout.tsx that wraps every page within that group. This enables you to load different sets of components, styles, or permission checks depending on the section of the site.

Root Layout (app/layout.tsx)

The root layout applies to every route in the application. It typically includes global providers, analytics, and metadata.

// https://github.com/woosal1337/blog/blob/main/app/layout.tsx
import "./globals.css";
import { ThemeProvider } from "next-themes";
import { Analytics } from "@vercel/analytics/react";

export default function RootLayout({
  children,
}: { children: React.ReactNode }) {
  return (
    <html lang="en" className="dark">
      <body className="bg-gray-900 text-white">
        <ThemeProvider attribute="class" defaultTheme="system">
          {children}
        </ThemeProvider>
        <Analytics />
      </body>
    </html>
  );
}

Website Group Layout (app/(website)/layout.tsx)

This layout wraps all public pages. It imports shared UI components like Navbar and applies content-specific styling.

// https://github.com/woosal1337/blog/blob/main/app/(website)/layout.tsx
import Navbar from "@/components/ui/navbar";

export default function WebsiteLayout({
  children,
}: { children: React.ReactNode }) {
  return (
    <>
      <Navbar />
      <main className="max-w-3xl mx-auto py-8">{children}</main>
    </>
  );
}

Admin Group Layout with Auth (app/(admin)/layout.tsx)

Route groups excel at enforcing cross-cutting concerns like authentication. By placing the auth check in the group layout, you protect every admin route simultaneously.

// https://github.com/woosal1337/blog/blob/main/app/(admin)/layout.tsx
import { redirect } from "next/navigation";
import Sidebar from "@/components/ui/admin-sidebar";

export default async function AdminLayout({
  children,
}: { children: React.ReactNode }) {
  // Simple server‑side auth check
  const session = await getServerSession(); // ← replace with your auth lib
  if (!session?.user?.isAdmin) {
    redirect("/login");
  }

  return (
    <div className="flex min-h-screen">
      <Sidebar />
      <section className="flex-1 p-6">{children}</section>
    </div>
  );
}

Creating Pages Within Route Groups

Pages inside route groups follow the same conventions as regular App Router pages. The group prefix is invisible to the user, so internal navigation and dynamic segments behave identically to standard routes.

Static Pages

Static pages work exactly as they do outside of groups. The file path app/(website)/about/page.tsx maps to the URL /about.

// https://github.com/woosal1337/blog/blob/main/app/(website)/about/page.tsx
export default function AboutPage() {
  return (
    <article>
      <h1 className="text-3xl font-bold">About Me</h1>
      <p className="mt-4">
        Hello! I’m Chele, a developer and writer. This page lives under the
        `(website)` route group but is served at <code>/about</code>.
      </p>
    </article>
  );
}

Dynamic Routes

Dynamic segments like [slug] function identically inside route groups. The group name is stripped while the dynamic parameter is preserved, so app/(website)/blog/[slug]/page.tsx becomes /blog/:slug.

// https://github.com/woosal1337/blog/blob/main/app/(website)/blog/[slug]/page.tsx
import { getPostBySlug } from "@/lib/blog";

export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getPostBySlug(params.slug);
  if (!post) {
    notFound();
  }

  return (
    <article className="prose">
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

Best Practices for Route Groups

When implementing route groups in your Next.js 14 application, keep these architectural guidelines in mind:

  • Place layout.tsx at the group root to share UI and server-side logic across the entire section.
  • Do not use route groups if you need the folder name in the URL—use standard folders instead.
  • Keep static assets in public/, as route groups only affect the routing layer, not asset serving.
  • Avoid conflicting routes by ensuring that two different groups do not resolve to the same URL path (e.g., two separate groups both containing /dashboard/page.tsx).

Summary

  • Route groups use parentheses (groupName) to organize files without affecting URL paths.
  • Each group supports its own layout.tsx for shared UI, data fetching, and server-side logic such as authentication checks.
  • Dynamic routes and API routes function identically inside groups, with the group segment omitted from the final URL.
  • The woosal1337/blog repository demonstrates this pattern by isolating public (website) and protected (admin) sections while maintaining clean, semantic URLs.

Frequently Asked Questions

Do route groups affect SEO or URL structure?

No, route groups are purely organizational. Next.js strips the parentheses-wrapped folder name from the URL path during build, so app/(marketing)/blog/page.tsx is served at /blog and search engines index it accordingly.

Can I nest route groups inside other route groups?

Yes, you can nest groups such as app/(shop)/(checkout)/cart/page.tsx, which resolves to /cart. This allows multiple levels of layout composition—for example, a shop-wide layout and a checkout-specific layout—while keeping the URL flat.

How do I protect an entire route group with authentication?

Implement the verification logic in the group's layout.tsx file. Use server-side functions like getServerSession() or cookies() to validate the request, and call redirect() from next/navigation if the user is unauthorized. This guards every page and API route within that group in a single location, as shown in app/(admin)/layout.tsx.

Are route groups available in the Next.js Pages Router?

No, route groups are exclusive to the App Router introduced in Next.js 13 and stabilized in Next.js 14. The legacy Pages Router uses the pages/ directory where every folder name automatically maps to a URL segment, offering no mechanism to hide path segments.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →