Next.js App Router: A Complete Beginner Tutorial
Learn Next.js App Router from scratch — file-based routing, server components, data fetching, layouts, loading states, error handling, and deployment.
What is Next.js?
Next.js is a React framework that adds server-side rendering, file-based routing, and full-stack capabilities to React. It's the most popular way to build production React applications.
The App Router (introduced in Next.js 13) is the modern routing system that uses React Server Components by default.
Getting Started
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
npm run dev
This creates a project with:
- TypeScript
- Tailwind CSS
- App Router (the
app/directory) - ESLint
File-Based Routing
In the App Router, the file system IS the router:
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/
│ └── page.tsx → /blog/my-post (dynamic)
├── api/
│ └── users/
│ └── route.ts → /api/users (API route)
├── layout.tsx → Root layout (wraps all pages)
├── loading.tsx → Loading UI
├── error.tsx → Error UI
└── not-found.tsx → 404 page
Basic Page
// app/page.tsx
export default function HomePage() {
return (
<main>
<h1>Welcome to My App</h1>
<p>This is the home page.</p>
</main>
);
}
Dynamic Routes
// app/blog/[slug]/page.tsx
interface Props {
params: Promise<{ slug: string }>;
}
export default async function BlogPost({ params }: Props) {
const { slug } = await params;
return (
<article>
<h1>Blog Post: {slug}</h1>
</article>
);
}
Server Components vs Client Components
By default, all components in the App Router are Server Components:
// This runs on the SERVER (default)
export default async function Page() {
// You can directly access databases, file system, env vars
const data = await fetch("https://api.example.com/data");
const json = await data.json();
return <div>{json.title}</div>;
}
For interactivity (useState, useEffect, onClick), use Client Components:
"use client"; // This directive makes it a Client Component
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
When to Use Each
| Feature | Server Component | Client Component |
|---|---|---|
| Fetch data | ✅ Direct await | ❌ Need useEffect |
| Access database | ✅ Yes | ❌ No |
| Use useState/useEffect | ❌ No | ✅ Yes |
| Event handlers (onClick) | ❌ No | ✅ Yes |
| Browser APIs | ❌ No | ✅ Yes |
| Reduce JS bundle | ✅ Yes | ❌ Adds to bundle |
Rule of thumb: Keep components as Server Components unless they need interactivity.
Layouts
Layouts wrap pages and persist across navigation:
// app/layout.tsx — Root layout (required)
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<nav>My App</nav>
<main>{children}</main>
<footer>© 2026</footer>
</body>
</html>
);
}
Nested Layouts
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex">
<aside>Sidebar</aside>
<main>{children}</main>
</div>
);
}
Data Fetching
Server Components (Recommended)
// Direct fetch in Server Components
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`, {
next: { revalidate: 3600 }, // Cache for 1 hour
});
return res.json();
}
export default async function UserPage({ params }: Props) {
const { id } = await params;
const user = await getUser(id);
return <h1>{user.name}</h1>;
}
Metadata (SEO)
// Static metadata
export const metadata = {
title: "My Page Title",
description: "Page description for SEO",
};
// Dynamic metadata
export async function generateMetadata({ params }: Props) {
const { slug } = await params;
const post = await getPost(slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
},
};
}
Generate meta tags with our Meta Tag Generator and preview them with our Google SERP Preview.
Loading and Error States
// app/blog/loading.tsx — Shows while page loads
export default function Loading() {
return <div>Loading...</div>;
}
// app/blog/error.tsx — Shows on error
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
API Routes
// app/api/users/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const users = await db.users.findMany();
return NextResponse.json(users);
}
export async function POST(request: Request) {
const body = await request.json();
const user = await db.users.create({ data: body });
return NextResponse.json(user, { status: 201 });
}
Deployment
Vercel (Recommended)
npm install -g vercel
vercel
Docker
Use our Dockerfile Generator to create an optimized Dockerfile for Next.js.
Related Tools & Tutorials
- Meta Tag Generator — Generate SEO meta tags
- Google SERP Preview — Preview search results
- JSON Formatter — Format API responses
- TypeScript Formatter — Format TypeScript code
- Dockerfile Generator — Docker setup for Next.js
- .gitignore Generator — Generate .gitignore
- Tailwind CSS Cheat Sheet — Tailwind reference