What is the App Router? (Next.js 13+ Routing System Guide)

December 28, 2025
The App Router is Next.js 13+'s new routing and rendering architecture, built on the app directory, nested layouts, and React Server Components, with support for Streaming, parallel routes, and cleaner data fetching patterns.
Next.js Core

What is the App Router?

The App Router is the new routing system and application architecture introduced in Next.js 13+. It uses the app/ directory for convention-based routing (where files define routes) and leverages React Server Components (RSC) as the default capability, fundamentally reimagining how we organize layouts, pages, loading states, error handling, and data fetching.

If you think of the Pages Router as "page-centric" (using pages/), the App Router is more "route segment and layout tree-centric" (using app/): each route segment can have its own layout, loading state, and error boundary, which compose together to form a complete UI tree.

Why Does It Matter?

  1. Enhanced layouts and navigation: Supports nested layouts, persistent layouts, and partial refreshes, reducing redundant renders and requests.
  2. Server-first by default (less JS): Pages are Server Components by default, allowing more rendering and data fetching on the server, reducing client-side bundle size.
  3. Better loading and error boundaries: Each route segment can independently define loading.tsx / error.tsx, improving user experience and maintainability.
  4. More natural full-stack capabilities: Route Handlers (app/api/**/route.ts) and Server Actions make monorepo full-stack development more seamless.
  5. Foundation for future features: Capabilities like parallel routes, intercepting routes, and Streaming/Suspense are more complete under the App Router.

Core Concepts & How It Works

Route Segments and Convention Files

In the App Router, a URL is typically determined by the directory structure under app/:

  • page.tsx: The page entry point for this segment
  • layout.tsx: The shared layout for this segment and its children (can be nested)
  • loading.tsx: The loading UI for this segment (based on Suspense)
  • error.tsx: The error boundary for this segment (must be a Client Component)
  • not-found.tsx: The 404 page for this segment

A minimal example:

// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
// app/page.tsx
export default function HomePage() {
  return <main>Home Page</main>;
}

Server Components vs Client Components

  • Server Components by default: Components without "use client" render on the server and can directly access server resources (databases, private APIs, environment variables, etc.).
  • Client Components for interactivity: Use these for button clicks, instant form validation, useState/useEffect, etc.
// app/dashboard/page.tsx (Server Component, default)
import { getServerSession } from "@/lib/auth"; // Example: server-side session access

export default async function DashboardPage() {
  const session = await getServerSession();
  return <div>Welcome, {session.user.name}</div>;
}
// app/dashboard/ClientButton.tsx (Client Component)
"use client";

import { useState } from "react";

export function ClientButton() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount((c) => c + 1)}>Clicks: {count}</button>;
}

Nested Layouts and Shared UI

App Router layouts compose in a tree structure. You can provide an independent layout for a specific segment while inheriting parent layouts:

// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="dashboard-shell">
      <aside>Sidebar</aside>
      <section>{children}</section>
    </div>
  );
}

Data Fetching and Caching (Intuitive Version)

In the App Router, the common recommendation is:

  • Fetch data directly in Server Components (simpler approach)
  • External fetch requests are cached and deduplicated by default (can configure cache / revalidate as needed)
  • For "update UI after submission," commonly use Server Actions or combine with revalidatePath/revalidateTag

(For deeper dives, see related entries on nextjs-server-actions and nextjs-server-components.)

Real-World Examples

Scenario 1: Building a Detail Page with Loading State and Error Boundary (Dynamic Route)

// app/posts/[slug]/page.tsx
import { notFound } from "next/navigation";

async function getPost(slug: string) {
  // Example: fetch from your API/database
  const res = await fetch(`https://example.com/api/posts/${slug}`, { cache: "no-store" });
  if (res.status === 404) return null;
  if (!res.ok) throw new Error("Failed to load");
  return res.json();
}

export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = await getPost(slug);

  if (!post) notFound();

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  );
}
// app/posts/[slug]/loading.tsx
export default function Loading() {
  return <div>Loading article...</div>;
}
// app/posts/[slug]/error.tsx
"use client";

export default function Error({ reset }: { reset: () => void }) {
  return (
    <div>
      <p>An error occurred while loading the article.</p>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

Explanation: page.tsx executes on the server and fetches data; while the request is pending, loading.tsx is displayed; errors are caught by error.tsx; missing resources trigger notFound() to enter the segment's 404 logic.

Scenario 2: Using Layout to Give an "Account Section" a Unified Shell and Authorization Check

// app/account/layout.tsx
import { redirect } from "next/navigation";
import { getServerSession } from "@/lib/auth"; // Example

export default async function AccountLayout({ children }: { children: React.ReactNode }) {
  const session = await getServerSession();
  if (!session) redirect("/login");

  return (
    <div className="account">
      <header>Account Center</header>
      <main>{children}</main>
    </div>
  );
}

Explanation: By placing "authentication + unified shell" in layout.tsx, you ensure all pages under app/account/** are automatically protected, while reducing repetitive authorization checks in each page.

Best Practices

💡 Tip 1: Use Server Components by default, only drop down to Client Components when interactivity is needed. This significantly reduces client-side JS and makes data access more intuitive.

  1. Place shared UI in the layout layer: Navigation, sidebars, authorization checks, global providers, etc., should primarily live in layout.tsx.
  2. Add boundary files for critical route segments: Adding loading.tsx / error.tsx / not-found.tsx to core pages will elevate user experience significantly.
  3. Make dynamic routes and data fetching predictable: Be explicit about which pages need real-time data (no-store) and which can use incremental updates (revalidate).
  4. Let route structure express business logic: For example, route groups like app/(marketing) or app/(app) (parenthesized directories) keep URLs clean while making structure clearer.

⚠️ Common Pitfall: Using browser APIs or React hooks (useState/useEffect) directly in Server Components. Components that need interactivity should have "use client" and maintain clear boundaries.

Common Questions

Do I have to choose between App Router and Pages Router?

Answer: No, they can coexist. You can migrate gradually, putting new features in app/ while keeping old pages in pages/ until migration is complete.

Why must my error.tsx be a Client Component?

Answer: Because error boundaries rely on client-side error recovery mechanisms (like reset()), Next.js requires error.tsx to have "use client".

Does layout.tsx re-render on every navigation?

Answer: Usually not. Layouts can persist (especially when navigating within the same layout tree), which is one of the key ways App Router improves experience and performance.

When do I need Route Handlers (app/api/**/route.ts)?

Answer: When you need to provide HTTP APIs (for third-party callbacks, webhooks, mobile apps, or external systems), Route Handlers are ideal; for form submissions alone, you might also consider Server Actions.

Comparison with Other Concepts

FeatureApp RouterPages Router
Routing directoryapp/pages/
Default rendering modeServer Components firstClient/server hybrid (page-centric)
Layout capabilitiesNested layouts, stronger persistenceRequires manual wrapping and composition
Loading/Error boundariesNative support at route segment levelRequires custom implementation
New feature supportParallel/intercepting routes, Server Actions, etc.Limited or requires additional solutions

Further Learning

  • nextjs-server-components
  • nextjs-server-actions
  • nextjs-middleware
  • nextjs-dynamic-routes
  • nextjs-parallel-routes
  • nextjs-metadata-api