Next.js Layouts, Templates, and Route Groups


The App Router makes route structure visible in the file tree, but its special files carry different navigation semantics. A layout.tsx persists. A template.tsx deliberately remounts. A folder wrapped in parentheses affects organization without becoming part of a URL. Treating all three as visual conveniences produces confusing state and accidental navigation behaviour.
The useful framing is architectural: decide which product shell must survive a transition, which experience should begin fresh, and which routes share an internal concern without sharing a path segment.
Route Structure Is Product Structure
Folders in app/ describe route segments. Pages are leaves; layouts compose around their descendants. That makes the tree a map of product boundaries, not simply a place to put files.
app/
layout.tsx
(marketing)/
layout.tsx
page.tsx
pricing/page.tsx
(product)/
dashboard/layout.tsx
dashboard/page.tsx
dashboard/settings/page.tsxThe parentheses are important. (marketing) and (product) organize code, but neither appears in the URL. /pricing remains /pricing; the group has not made it /marketing/pricing.
Start with user journeys, then let them determine the tree. A shared authenticated application frame is usually a stable layout boundary. An onboarding wizard that must restart local state on every step is a candidate for a template. A marketing site and a product application are often separate route groups.
This choice is easier when you write down the transition you are designing. Ask what should visibly stay put when someone moves from an invoice to a report. If the answer is the workspace navigation, account identity, and the broad page frame, those belong above both leaves. Ask what should restart when someone opens a new compose screen. If the answer is a local editor, autofocus, and animation sequence, that is a reset boundary. The route tree becomes clear once persistence has an explicit owner.
Use Layouts for Persistent Shared UI
Layouts wrap child segments and persist across navigation within their scope. That makes them appropriate for app shells: navigation, a signed-in identity, stable providers, and other UI a user should not experience as disappearing on every click.
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="grid min-h-screen grid-cols-[16rem_1fr]">
<aside>
<DashboardNavigation />
</aside>
<main>{children}</main>
</div>
);
}When /dashboard changes to /dashboard/settings, the dashboard layout remains in place. That persistence is a feature, but it is also a constraint: state inside a child Client Component can remain alive longer than expected. Put durable state there intentionally; do not use a layout merely because two pages happen to look similar today.
Think about data ownership as carefully as UI ownership. A server layout can load a workspace name or permissions once for the shared frame. It should not eagerly fetch every piece of page-specific data just because it sits above the page. Page-specific data stays at the leaf, where loading and error boundaries can describe its actual failure mode. This keeps a slow report query from delaying an otherwise usable application shell.
Persist What a User Perceives as Stable
Navigation chrome and a long-lived workspace belong in a layout. A page-local search query, unsaved wizard step, or animation entrance usually does not.
Use Templates When Reset Is the Feature
template.tsx looks similar to a layout but receives a unique key during navigation. Its children remount when that segment changes. This is useful when a reset is product behaviour: replaying an entrance animation, resynchronizing an effect, or clearing client state at a route transition.
// app/dashboard/template.tsx
export default function DashboardTemplate({
children,
}: {
children: React.ReactNode;
}) {
return <section className="animate-in fade-in">{children}</section>;
}Use a template sparingly. A blanket template can make a product feel less continuous and can hide an underlying state-ownership issue. Start with a layout; introduce a template only when remounting is explicitly desired and test the transition with real navigation.
The distinction matters for effects. A Client Component inside a persistent layout does not automatically treat a navigation as a fresh mount. If you need an effect to respond to a segment change, either model that input explicitly or choose a template at the intended segment. Do not rely on unmount cleanup as a navigation mechanism when the component was intentionally placed in a persistent layer.
Trace the route layers
Switch routes to see a simplified, browser-only model of the wrappers that compose a route. It illustrates structure; Next.js performs the actual server navigation.
Route-composition simulator
This is a browser simulation of which App Router layers compose a route.
/dashboard: The dashboard layout persists while the overview page is the active leaf. The template remounts its children for this segment.
export default function Layout({ children }: Props) {
return <AppShell>{children}</AppShell>;
}Use Route Groups Without Changing URLs
Route groups allow an internal taxonomy that does not leak into public addresses. They are useful for splitting a site by team, product area, or experience while retaining friendly URLs.
app/
(marketing)/about/page.tsx -> /about
(shop)/cart/page.tsx -> /cartGroups can also opt a subset of routes into a nested layout. Put authenticated billing and account screens in (account) so they share an account shell while checkout remains outside it.
There are two caveats worth treating as design constraints. First, two groups cannot create the same resolved path: (marketing)/about and (company)/about conflict. Second, crossing between different root layouts triggers a full page load. Multiple root layouts are powerful for genuinely separate experiences, but they are not a free styling tool.
Route groups are also useful during a migration. A team can move a cluster of pages into (workspace) without changing inbound links, then add a shared nested layout once the ownership is understood. That is safer than changing public URL structure and visual composition in one large pull request. The URL remains a stable contract while the implementation gains a clearer seam.
Model a Real Application Shell
Here is a practical shape for a product with a public site and a signed-in workspace:
app/
(public)/
layout.tsx # public header and footer
page.tsx # /
resources/page.tsx # /resources
(workspace)/
layout.tsx # authenticated root shell
dashboard/
layout.tsx # dashboard navigation
page.tsx # /dashboard
reports/page.tsx # /dashboard/reports
settings/page.tsx # /dashboard/settingsIf public and workspace areas must be separate root layouts, make the full reload between them an intentional trade-off. If they can share a root layout, keep one and use nested layouts for presentation differences. The latter often preserves smoother transitions and simpler metadata.
For a layout that owns data access, keep its server/client boundary narrow. Fetch secure shell data on the server and pass a small serializable view model to client navigation. Avoid marking the entire shell as a Client Component because one button needs state.
Before landing this tree, manually test the transitions that matter: direct-load a deep route, move between sibling pages, move between groups, and use the browser back button. Watch which client state stays alive and which loading UI appears. These are product behaviours, so they deserve a navigation test rather than a file-tree review alone.
Avoid Structural Traps
- Do not build a global layout that knows every page's special case. Move local chrome down to the nearest shared segment.
- Do not use a route group to solve an authorization problem. A group is organization; enforce access in server-side code and route-level logic.
- Do not put URL meaning into a group name. Users and analytics only see the resolved path.
- Do not add a template to fix stale state without identifying who should own and reset that state.
- Do not create duplicate routes through different groups; the conflict is architectural, not a naming inconvenience.
Key Takeaways
- Layouts persist and are for stable shared product UI.
- Templates remount descendants and are for deliberate reset behaviour.
- Route groups organize code and layout scope without changing URLs.
- Multiple root layouts create distinct experiences but make cross-root navigation a full load.
- Put each wrapper at the smallest route boundary that matches the user journey.
Next, we will make route transitions robust under failure by building loading, error, and not-found states that belong to the same segment boundaries.