- Published on
Next.js App Router - The Complete Guide for 2026
- Authors

- Name
- Sanjeev Sharma
- @webcoderspeed1
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
- File Structure
- Root Layout
- Pages and Navigation
- Data Fetching — No More getServerSideProps!
- Nested Layouts
- Loading UI — Built-in Suspense
- Error Handling
- API Routes with Route Handlers
- Server vs Client Components
- Metadata API
- Conclusion
App Router vs Pages Router
| Feature | Pages Router | App Router |
|---|---|---|
| Server Components | ❌ | ✅ |
| Nested Layouts | ❌ | ✅ |
| Streaming | Partial | Full |
| Data Fetching | getServerSideProps | async/await in components |
| Loading States | Manual | Built-in loading.tsx |
| Error Handling | Custom _error.js | Built-in error.tsx |
File Structure
The App Router uses the app/ directory:
app/
├── layout.tsx ← Root layout (required)
├── page.tsx ← Home page (/)
├── loading.tsx ← Loading UI
├── error.tsx ← Error UI
├── not-found.tsx ← 404 page
├── blog/
│ ├── page.tsx ← /blog
│ └── [slug]/
│ └── page.tsx ← /blog/[slug]
├── dashboard/
│ ├── layout.tsx ← Nested layout
│ ├── page.tsx ← /dashboard
│ └── settings/
│ └── page.tsx ← /dashboard/settings
└── api/
└── users/
└── route.ts ← API route
Root Layout
Every App Router project needs a root 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
// This is a Server Component by default
export default function HomePage() {
return (
<main>
<h1>Welcome to My App</h1>
</main>
)
}
// 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:
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:
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:
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
'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
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 })
}
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
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.