DTTooleras

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.

DevToolsHub Team26 min read829 words

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

FeatureServer ComponentClient 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

nextjsnext.jsapp routerreactnextjs tutorialserver componentsnextjs beginner

Related articles

All articles

Practice with free tools

200+ free developer tools that run in your browser.

Browse all tools →