---
name: nextjs-app-router-mastery
description: Next.js 14+ App Router patterns, server components, and data fetching
license: MIT
compatibility: nextjs 14+, react 18+
allowed-tools: read_file write_file apply_patch search_with_context run_command
---
# Next.js App Router Mastery
## Core Concepts
1. **Server Components by Default** - Components are server-rendered unless marked `'use client'`
2. **Streaming & Suspense** - Progressive rendering with loading states
3. **Parallel Routes** - Simultaneous route rendering
4. **Intercepting Routes** - Modal patterns without navigation
## File Conventions
```
app/
layout.tsx # Root layout (required)
page.tsx # Route UI
loading.tsx # Loading UI (Suspense boundary)
error.tsx # Error boundary
not-found.tsx # 404 UI
route.ts # API route handler
template.tsx # Re-renders on navigation
default.tsx # Parallel route fallback
```
## Data Fetching Patterns
### Server Component Fetching
```typescript
// app/posts/page.tsx - Server Component
async function PostsPage() {
const posts = await fetchPosts(); // Direct fetch, no useEffect
return ;
}
```
### Parallel Data Fetching
```typescript
async function Dashboard() {
// Parallel fetches - don't await sequentially
const [user, posts, analytics] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchAnalytics(),
]);
return ;
}
```
### Streaming with Suspense
```typescript
export default function Page() {
return (
Dashboard
}>
{/* Async component */}
}>
{/* Streams in when ready */}
);
}
```
## Server Actions
```typescript
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
await db.posts.create({ data: { title } });
revalidatePath('/posts');
}
```
## Route Handlers
```typescript
// app/api/posts/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const posts = await fetchPosts();
return NextResponse.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await createPost(body);
return NextResponse.json(post, { status: 201 });
}
```
## Caching Strategies
```typescript
// Force dynamic rendering
export const dynamic = 'force-dynamic';
// Revalidate every 60 seconds
export const revalidate = 60;
// Static generation
export const dynamic = 'force-static';
// Per-fetch revalidation
fetch(url, { next: { revalidate: 3600 } });
// On-demand revalidation
revalidatePath('/posts');
revalidateTag('posts');
```
## Metadata
```typescript
// Static metadata
export const metadata: Metadata = {
title: 'My App',
description: 'App description',
};
// Dynamic metadata
export async function generateMetadata({ params }): Promise {
const post = await fetchPost(params.id);
return { title: post.title };
}
```
## Best Practices
1. Keep client components at the leaves of the tree
2. Pass serializable props from server to client components
3. Use `loading.tsx` for route-level loading states
4. Colocate data fetching with the component that uses it
5. Use route groups `(group)` for organization without affecting URL