EEPNext.js Template
Architecture

Routing and Layouts

How the app/ directory is structured, what route groups are, and how layouts nest.

The app/ boundary

app/ holds only Next.js file-convention files - page.tsx, layout.tsx, error.tsx, not-found.tsx, loading.tsx, route.ts. These are reserved filenames the router discovers by location, so they must live here.


Route groups

Parentheses folders - (name)/ - are Next.js route groups. The name is invisible to the URL: app/(app)/dashboard/page.tsx is still /dashboard. Their only purpose is layout segmentation - grouping pages that share a common layout without that grouping appearing in the URL.

This template ships with two route groups plus a restricted catch-all:

layout.tsx// root: ClerkProvider, ThemeProvider (every route)
error.tsx// app-wide error boundary page
not-found.tsx// 404 page
layout.tsx// auth gate + Navbar (authenticated app pages)
page.tsx// /
layout.tsx// bare shell - no navbar, no auth gate
page.tsx// /sign-in
page.tsx// /sign-up
page.tsx// /restricted - shown when a signed-in user lacks the required role
...// route handlers

The route groups and their purpose at a glance:

LocationWhat lives hereRule
app/Next.js file-convention files onlypage.tsx, layout.tsx, error.tsx, not-found.tsx etc. - thin shells that delegate to components/ and classes/. No business logic.
app/(app)/Authenticated app pagesAll pages that require a signed-in user. Layout mounts the auth gate and navbar automatically.
app/(auth)/Auth pagesSign-in and sign-up. Bare layout - no navbar, no auth gate.
app/restricted/Role-based access denialShown when a signed-in user lacks a qualifying role (Admin or SuperAdmin) for app access. Lives outside (app)/ so it can be reached without passing the role check again.

Layout nesting

Layouts nest from the outside in. Every request passes through all layouts from the root down to the matched page:

Root layout.tsx         - ClerkProvider, ThemeProvider, AppToaster
  └─ (app)/layout.tsx   - RootLayoutClient (auth gate), Navbar
       └─ page.tsx      - the actual page content

For auth routes the path is different:

Root layout.tsx         - ClerkProvider, ThemeProvider, AppToaster
  └─ (auth)/layout.tsx  - bare <>{children}</> - no gate, no navbar
       └─ sign-in/      - Clerk's sign-in UI

This is why the auth gate lives in (app)/layout.tsx and not in the root layout.tsx. If the gate were in the root layout it would intercept every route - including sign-in - and redirect to /sign-in forever.

Never put auth gating in the root layout.tsx. It runs on every route including the auth pages themselves, causing an infinite redirect loop. Gate only in the (app)/ layout.


Adding a new page

New authenticated pages go inside (app)/:

page.tsx// /dashboard - automatically gets Navbar + auth gate

Adding a new route group

Add a new route group when a set of pages needs a different layout from the rest of the app - a public marketing section, a full-screen onboarding flow, or an admin panel with a sidebar.

layout.tsx// authenticated app - Navbar + auth gate
layout.tsx// Clerk auth flows - bare layout
layout.tsx// public pages - different header, no auth
page.tsx// /landing

Each group gets its own layout.tsx. The root layout.tsx only provides global providers - it never makes assumptions about which group is rendering.

Shared layout elements - navbars, sidebars, footers - belong in the relevant group layout, not on individual pages.


On this page