Last Updated: November 21, 2025
App Router Structure
app/
├── layout.js # Root layout
├── page.js # Home page (/)
├── loading.js # Loading UI
├── error.js # Error handling
├── not-found.js # 404 page
├── about/
│ └── page.js # /about route
├── blog/
│ ├── page.js # /blog
│ └── [slug]/
│ └── page.js # /blog/:slug
└── api/
└── users/
└── route.js # API route
Server Components (default)
// app/page.js - Server Component by default
async function getData() {
const res = await fetch('https://api.example.com/data', {
cache: 'no-store' // SSR
// cache: 'force-cache' // SSG
// next: { revalidate: 60 } // ISR
});
return res.json();
}
export default async function Page() {
const data = await getData();
return (
<div>
<h1>{data.title}</h1>
</div>
);
}
Client Components
'use client'; // Add this directive at top
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Dynamic Routes
// app/blog/[slug]/page.js
export default function BlogPost({ params }) {
return <h1>Post: {params.slug}</h1>;
}
// Generate static params for SSG
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
// Catch-all routes: [...slug]
// Optional catch-all: [[...slug]]
API Routes
// app/api/users/route.js
import { NextResponse } from 'next/server';
export async function GET(request) {
const users = await getUsers();
return NextResponse.json(users);
}
export async function POST(request) {
const body = await request.json();
const user = await createUser(body);
return NextResponse.json(user, { status: 201 });
}
Metadata & SEO
// Static metadata
export const metadata = {
title: 'My App',
description: 'App description',
};
// Dynamic metadata
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
};
}
💡 Pro Tip:
Use Server Components by default, only add 'use client' when you need interactivity!