Published on

Next.js App Router - The Complete Guide for 2026

Authors

Introduction

The Next.js App Router is now the default and recommended way to build Next.js applications. It brings a fundamentally new architecture — file-based routing with layouts, React Server Components, streaming, and built-in data fetching patterns.

If you're still on the Pages Router, this guide will get you up to speed fast.

App Router vs Pages Router

FeaturePages RouterApp Router
Server Components
Nested Layouts
StreamingPartialFull
Data FetchinggetServerSidePropsasync/await in components
Loading StatesManualBuilt-in loading.tsx
Error HandlingCustom _error.jsBuilt-in error.tsx

File Structure

The App Router uses the app/ directory:

app/
├── layout.tsxRoot layout (required)
├── page.tsxHome page (/)
├── loading.tsxLoading UI
├── error.tsxError UI
├── not-found.tsx404 page
├── blog/
│   ├── page.tsx/blog
│   └── [slug]/
│       └── page.tsx/blog/[slug]
├── dashboard/
│   ├── layout.tsxNested layout
│   ├── page.tsx/dashboard
│   └── settings/
│       └── page.tsx/dashboard/settings
└── api/
    └── users/
        └── route.tsAPI route

Root Layout

Every App Router project needs a root layout.tsx:

app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'My App',
  description: 'Built with Next.js App Router',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <nav>My Navigation</nav>
        {children}
        <footer>My Footer</footer>
      </body>
    </html>
  )
}

Pages and Navigation

app/page.tsx
// This is a Server Component by default
export default function HomePage() {
  return (
    <main>
      <h1>Welcome to My App</h1>
    </main>
  )
}
app/blog/[slug]/page.tsx
// Dynamic route
interface Props {
  params: { slug: string }
}

export default async function BlogPost({ params }: Props) {
  const post = await getPost(params.slug)

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

// Optional: generate static paths
export async function generateStaticParams() {
  const posts = await getAllPosts()
  return posts.map(post => ({ slug: post.slug }))
}

Data Fetching — No More getServerSideProps!

In the App Router, you just async/await directly in your Server Components:

app/users/page.tsx
async function getUsers() {
  const res = await fetch('https://api.example.com/users', {
    cache: 'no-store',  // SSR — fresh on every request
    // next: { revalidate: 60 }  // ISR — revalidate every 60s
    // (no option) = static    // SSG — cached at build time
  })
  return res.json()
}

export default async function UsersPage() {
  const users = await getUsers()  // Direct async call!

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

Nested Layouts

Layouts persist between navigation — great for shared UI like sidebars:

app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="dashboard">
      <aside>
        <nav>
          <a href="/dashboard">Overview</a>
          <a href="/dashboard/settings">Settings</a>
        </nav>
      </aside>
      <main>{children}</main>
    </div>
  )
}

Loading UI — Built-in Suspense

Create a loading.tsx file for automatic loading states:

app/dashboard/loading.tsx
export default function DashboardLoading() {
  return (
    <div className="skeleton">
      <div className="skeleton-title" />
      <div className="skeleton-text" />
    </div>
  )
}

Next.js automatically wraps your page in a <Suspense> boundary with this loading UI!

Error Handling

app/dashboard/error.tsx
'use client'  // Error boundaries must be Client Components

export default function DashboardError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Try again</button>
    </div>
  )
}

API Routes with Route Handlers

app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'

// GET /api/users
export async function GET(request: NextRequest) {
  const users = await db.users.findAll()
  return NextResponse.json(users)
}

// POST /api/users
export async function POST(request: NextRequest) {
  const body = await request.json()
  const user = await db.users.create(body)
  return NextResponse.json(user, { status: 201 })
}
app/api/users/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const user = await db.users.findById(params.id)
  if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 })
  return NextResponse.json(user)
}

Server vs Client Components

// Server Component (default) — can fetch data, can't use hooks
async function ServerComponent() {
  const data = await fetch('/api/data')  // ✅
  const [state, setState] = useState()  // ❌ Not allowed!
  return <div>{data}</div>
}

// Client Component — add 'use client' directive
'use client'

import { useState } from 'react'

function ClientComponent() {
  const [count, setCount] = useState(0)  // ✅
  const data = await fetch('/api/data')  // ❌ Not allowed!
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

Metadata API

app/blog/[slug]/page.tsx
import { Metadata } from 'next'

export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug)
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      images: [post.coverImage],
    },
  }
}

Conclusion

The Next.js App Router is a paradigm shift in how React applications are built. Server Components, nested layouts, built-in loading and error states, and the simplified data fetching model make it a joy to work with. If you're starting a new project in 2026, the App Router is the way to go — no question.