The Anatomy of the App Router

In the App Router, folders define the URL structure, but special files define the UI behavior. This convention over configuration approach allows Next.js to handle complex React features like Suspense and Error Boundaries automatically.


File Based Conventions

Each folder in our app directory represents a route segment. Within these segments, Next.js looks for specific filenames to build the component tree.

1. page.tsx

This is the only file that makes a route publicly accessible. It defines the unique UI for that specific path.

  • Role: The leaf of the route tree.
  • Context: Can be a Server or Client component.

2. layout.tsx

Used to share UI across multiple pages (e.g., Navbar, Sidebar).

  • Persistence: Layouts do not re-render when navigating between sibling pages. They preserve state (like search input text or scroll position).
  • Nesting: Layouts wrap the child segments automatically.

3. template.tsx

Similar to layouts, but with one key difference: Templates create a new instance for each child on navigation.

  • Use Case: CSS animations that need to re-trigger on every page change, or useEffect hooks that must run every time we switch pages.
  • Note: Use layout.tsx by default; only use template.tsx if you specifically need the "mount/unmount" behavior.

4. loading.tsx

Next.js uses this file to create an automatic React Suspense boundary around our page.tsx.

  • The Benefit: While our Server Component is fetching data, the user immediately sees the loading.tsx UI (like a skeleton screen) instead of a blank page.

5. error.tsx & not-found.tsx

  • error.tsx: An Error Boundary that catches runtime exceptions in its segment. It must be a Client Component because it needs to provide a Try Again button.
  • not-found.tsx: Triggered when the notFound() function is called or when a route doesn't exist.

📝 File Hierarchy & Nesting

When we visit a URL, Next.js assembles the components in a specific order. Imagine the folder structure: /dashboard/settings. The hierarchy looks like this:

  1. layout.tsx (Root)
  2. layout.tsx (Dashboard)
  3. error.tsx (Dashboard)
  4. loading.tsx (Dashboard)
  5. page.tsx (Settings)

Key Rule: A layout in a parent folder wraps the layouts and pages in its child folders.


🛑 Stop and Think

The beauty of loading.tsx and error.tsx is that they allow us to handle 'unhappy paths' declaratively. We don't need if (loading) or try/catch logic inside our components; the file system handles the switching for us.


📝 Summary: File Roles

FilePurposeRe-renders on Nav?Component Type
page.tsxUnique Route UIYesServer/Client
layout.tsxShared/Persistent UINoServer (Default)
template.tsxShared UI (Resetting)YesServer/Client
loading.tsxSuspense FallbackN/AServer/Client
error.tsxError RecoveryN/AClient Only

Next.js provides specialized folder patterns that allow us to manipulate the URL structure and organize code without cluttering the browser's address bar.


1. Route Groups (folder)

Sometimes we want to group routes together (e.g., all Auth pages or all Dashboard pages) but we don't want the folder name to appear in the URL.

  • Syntax: Wrap the folder name in parentheses: (auth).
  • The URL: app/(auth)/login/page.tsx becomes your-domain.com/login.
  • The Benefit: We can create different layouts for different groups. For example, all pages in (auth) can share a layout with no navbar, while pages in (marketing) share a layout with a big hero section.

2. Private Folders

By default, every folder in app is a potential route segment. If we want to keep your components, hooks inside the route folder but prevent them from being accessible via a URL, use an underscore.

  • Syntax: Prefix the folder with an underscore: _components.
  • Result: Next.js and its routing system will completely ignore this folder.
  • Use Case: Colocation. Keeping the components used only by a specific route right next to the page.tsx without accidentally creating a /components URL.

3. Dynamic Routes [slug]

When we don't know the exact segment name ahead of time (e.g., a blog post ID or a username), we use dynamic segments.

  • Syntax: Wrap the folder name in brackets: [id].
  • Accessing Data: The value is passed to the page.tsx via the params prop.
// app/blog/[slug]/page.tsx
export default function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const data = await params;
  return <h1>Reading: {data.slug}</h1>;
}

4. Catch-all Segments [...slug]

If you need to match multiple levels of a URL (e.g., a documentation site with infinite nested categories), use the spread operator.

  • Syntax: [...slug] (matches /docs/setup, /docs/setup/linux, etc.)
  • Optional Catch-all: [[...slug]] (also matches the root /docs without extra segments).

📝 Organizational Summary

PatternFolder SyntaxURL ImpactBest For
Standard Routedashboard/dashboardRegular pages.
Route Group(marketing)NoneShared layouts without URL bloat.
Private Folder_libHiddenColocating code, components, or styles.
Dynamic Route[id]/123Dynamic content like IDs or slugs.
Catch-all[...slug]/a/b/cComplex docs or folder navigations.

🛑 Stop and Think

Route Groups are our best friend for cleaner architecture. Instead of having a messy root app folder, group features into (main), (auth), and (admin). This keeps our layouts organized and your URL structure exactly how we want it.