How and Why to use Route Groups in NextJS?

Route groups in Next.js 14 App Router let you organize routes without affecting the URL structure. This is particularly useful when managing large applications with different sections. In this tutorial, we’ll explore how route groups work, how to set up layouts, and how nested components settle with their parent layouts.

Setting Up a Route Group

Let’s start by organizing our Recipe App into public-facing pages (such as recipe listings) and admin pages (for managing recipes). We’ll use route groups to keep the structure clean and manageable.

Here’s a tree structure for the app:

src/
└── app/
    β”œβ”€β”€ (public)/
    β”‚   β”œβ”€β”€ layout.tsx
    β”‚   β”œβ”€β”€ page.tsx
    β”‚   └── recipes/
    β”‚       └── page.tsx
    └── (admin)/
        β”œβ”€β”€ layout.tsx
        β”œβ”€β”€ dashboard/
        β”‚   └── page.tsx
        └── recipes/
            └── add/
                └── page.tsx

Public Layout and Page Example

The (public) folder represents public-facing pages like the home page and recipe listings. This folder won’t affect the URL structure, meaning /recipes will still work as expected, even though the page is inside the (public) folder.

// src/app/(public)/layout.tsx
export default function PublicLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <header>
          <h1>Recipe App</h1>
          <nav>
            <a href="/">Home</a> | <a href="/recipes">Recipes</a>
          </nav>
        </header>
        <main>{children}</main>
        <footer>Β© 2024 Recipe App</footer>
      </body>
    </html>
  );
}

// src/app/(public)/page.tsx
export default function HomePage() {
  return (
    <section>
      <h2>Welcome to the Recipe App</h2>
      <p>Find and share the best recipes!</p>
    </section>
  );
}

In this example, layout.tsx wraps all public pages, such as /recipes, with a shared header and footer.

Final HTML output for /:

<html lang="en">
  <body>
    <header>
      <h1>Recipe App</h1>
      <nav>
        <a href="/">Home</a> | <a href="/recipes">Recipes</a>
      </nav>
    </header>
    <main>
      <section>
        <h2>All Recipes</h2>
        <p>Find and share the best recipes!</p>
      </section>
    </main>
    <footer>Β© 2024 Recipe App</footer>
  </body>
</html>

Nested Route Groups

How nested RouteGroup works in Next.js?

You can nest route groups to organize different sections of your app. For example, you can group admin pages under an (admin) folder, which will include pages like the dashboard and add recipe.

// src/app/(admin)/layout.tsx
export default function AdminLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <nav>
          <a href="/dashboard">Dashboard</a> | <a href="/admin/recipes/add">Add Recipe</a>
        </nav>
        <main>{children}</main>
      </body>
    </html>
  );
}

// src/app/(admin)/dashboard/page.tsx
export default function DashboardPage() {
  return (
    <section>
      <h2>Admin Dashboard</h2>
      <p>Manage your recipes and content.</p>
    </section>
  );
}

In this case, /dashboard uses the admin layout, which provides navigation links specific to admin actions, like adding recipes.

Final HTML output for /dashboard:

<html lang="en">
  <body>
    <nav>
      <a href="/dashboard">Dashboard</a> | <a href="/admin/recipes/add">Add Recipe</a>
    </nav>
    <main>
      <section>
        <h2>Admin Dashboard</h2>
        <p>Manage your recipes and content.</p>
      </section>
    </main>
  </body>
</html>

Nested Routes for Admin Actions (Adding Recipes)

Nested route groups help you organize admin actions, such as adding new recipes. Let’s look at the add recipe page under the admin section:

// src/app/(admin)/recipes/add/page.tsx
export default function AddRecipePage() {
  return (
    <section>
      <h2>Add New Recipe</h2>
      <form>
        <label htmlFor="recipe-name">Recipe Name:</label>
        <input type="text" id="recipe-name" />
        <button type="submit">Add Recipe</button>
      </form>
    </section>
  );
}

This page maps to /admin/recipes/add and is wrapped with the admin layout. The AddRecipePage component will be rendered inside the <main> tag of AdminLayout.

Final HTML output for /admin/recipes/add:

<html lang="en">
  <body>
    <nav>
      <a href="/dashboard">Dashboard</a> | <a href="/admin/recipes/add">Add Recipe</a>
    </nav>
    <main>
      <section>
        <h2>Add New Recipe</h2>
        <form>
          <label for="recipe-name">Recipe Name:</label>
          <input type="text" id="recipe-name" />
          <button type="submit">Add Recipe</button>
        </form>
      </section>
    </main>
  </body>
</html>

Excluding Layouts from Specific Routes

Sometimes, you want specific pages not to inherit a layout. To exclude a layout, you can create an empty layout file for that route group. Here’s how you can exclude the layout for a specific page:

// src/app/(public)/(no-layout)/layout.tsx
export default function NoLayout({ children }: { children: React.ReactNode }) {
  return <>{children}</>; // No layout, just render the content
}

// src/app/(public)/(no-layout)/page.tsx
export default function NoLayoutPage() {
  return (
    <section>
      <h2>No Layout Page</h2>
      <p>This page does not use any layout.</p>
    </section>
  );
}

In this setup, the NoLayoutPage will not inherit the public layout, keeping the page isolated from any shared components.

Final HTML output for /no-layout:

<html lang="en">
  <body>
    <section>
      <h2>No Layout Page</h2>
      <p>This page does not use any layout.</p>
    </section>
  </body>
</html>

Summary

Route groups in Next.js 14 App Router let you organize your app without impacting the URL structure. By grouping public and admin pages under different folders, you can manage layouts more effectively and keep your app structure clean. Nested route groups make it easy to manage different sections of your app, and you can even exclude layouts for specific pages to tailor their structure. Each nested component smoothly integrates with the parent layout, making your app both maintainable and scalable.

Clap here if you liked the blog