Next.js App Router Guide — React Server Components and Server Actions

Master Next.js App Router with React Server Components, Server Actions, layouts, and streaming. Migrate from Pages Router.

What Is the App Router?

Next.js 13 introduced the App Router, a new routing paradigm built on React Server Components (RSC). Unlike the Pages Router, all components in app/ are server components by default — they render on the server and send HTML to the client with zero JavaScript bundle cost.

The App Router enables layouts, nested routing, loading states, and error boundaries at every route segment, making it far more compositional than the old pages/ directory.

File System Conventions

app/
├── layout.tsx          # Root layout (persistent across routes)
├── page.tsx            # Home page (/)
├── loading.tsx         # Suspense fallback for this segment
├── error.tsx           # Error boundary for this segment
├── not-found.tsx       # 404 for this segment
├── dashboard/
│   ├── layout.tsx      # Dashboard layout
│   ├── page.tsx        # /dashboard
│   └── settings/
│       └── page.tsx    # /dashboard/settings
└── api/
    └── users/
        └── route.ts    # API route: GET/POST /api/users

Server Components vs Client Components

// Server Component (default) — runs only on server
// Can fetch data, read files, access databases directly
async function UserList() {
  const users = await db.query('SELECT * FROM users')
  return 
    {users.map(u =>
  • {u.name}
  • )}
} // Client Component — add 'use client' directive 'use client' import { useState } from 'react' function Counter() { const [count, setCount] = useState(0) return }

Key rule: Server Components can import Client Components, but not vice versa. Pass server data to client components via props.

Data Fetching

// Fetch in Server Components — no useEffect needed
async function ProductPage({ params }: { params: { id: string } }) {
  // Parallel fetching
  const [product, reviews] = await Promise.all([
    fetch(`/api/products/${params.id}`).then(r => r.json()),
    fetch(`/api/products/${params.id}/reviews`).then(r => r.json()),
  ])
  
  return (
    <div>
      <h1>{product.name}</h1>
      <ReviewList reviews={reviews} />
    </div>
  )
}

Next.js extends the native fetch API with caching options: cache: 'force-cache' (static), cache: 'no-store' (dynamic), or next: { revalidate: 60 } (ISR).

Server Actions

'use server'

// app/actions.ts
export async function createUser(formData: FormData) {
  const name = formData.get('name') as string
  await db.insert(users).values({ name })
  revalidatePath('/users')
}

// Use in a Client Component
'use client'
import { createUser } from './actions'

export function CreateUserForm() {
  return (
    <form action={createUser}>
      <input name="name" />
      <button type="submit">Create</button>
    </form>
  )
}

Frequently Asked Questions

When should I use the Pages Router vs App Router?

For new projects, use App Router. For existing projects, both coexist — you can migrate incrementally. The App Router has better defaults for performance but has a steeper learning curve.

How do I handle authentication with App Router?

Middleware (middleware.ts at the root) runs before every request and can redirect unauthenticated users. Libraries like NextAuth.js v5 (Auth.js) are built for App Router.

Can I use Redux or Zustand with Server Components?

State management libraries only work in Client Components. A common pattern: fetch data in Server Components, pass it as props, then use client-side state for UI interactions only.

🚀 Recommended: Deploy This on Hostinger VPS

The fastest way to get this running in production is a Hostinger VPS — starting at $3.99/mo, includes one-click Docker support, full root access, and SSD storage. Readers of this guide can use the link below for up to 75% off.

Get Hostinger VPS → Affiliate link — we may earn a commission at no extra cost to you.