---
name: "Next.js App Router & Server Components"
description: "Build Next.js 15 applications using App Router, Server Components, Client Components, Server Actions, and streaming. Apply when creating pages, handling data fetching, implementing routes, or optimizing performance."
allowed-tools: Read, Write, Edit, Bash
version: 1.1.0
compatibility: Claude Opus 4.5, Claude Code v2.x
updated: 2026-01-24
---
# Next.js App Router & Server Components
Systematic Next.js 15 development with App Router, Server Components, and performance optimization.
## Overview
This Skill enforces:
- Server Components by default (use client only when needed)
- Proper data fetching patterns
- File-based routing with App Router
- Server Actions for mutations
- Streaming and Suspense boundaries
- Performance optimization
- Route handlers and middleware
Apply when building Next.js pages, implementing routes, or optimizing performance.
## Server vs Client Components
**Default: Server Components** (no directive needed)
**When needed: Client Components** (add `'use client'`)
### Decision Tree
```
Does component need browser APIs?
├─ YES → Client Component ('use client')
└─ NO → Server Component (default)
Does component need event handlers?
├─ YES → Client Component
└─ NO → Server Component
Does component need state or effects?
├─ YES → Client Component
└─ NO → Server Component
Does component fetch data?
├─ YES → Server Component (preferred)
└─ NO → Check other criteria
```
## Server Components (Default)
### Benefits
- Runs on server only
- Zero JavaScript sent to browser
- Direct database access
- Access to secrets
- Better SEO
- Faster initial load
### Example
```tsx
// app/users/page.tsx
// No 'use client' = Server Component
import { db } from '@/lib/db';
export default async function UsersPage() {
// Direct database query (server-only)
const users = await db.user.findMany();
return (
Users
{users.map(user => (
{user.name}
))}
);
}
```
### Data Fetching in Server Components
```tsx
// ✅ GOOD: Async component with await
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // Cache for 1 hour
});
const result = await data.json();
return
);
}
// ❌ BAD: useEffect in Server Component
export default function Page() {
useEffect(() => {
// This doesn't work in Server Components!
fetchData();
}, []);
}
```
## Client Components
### When to Use
Add `'use client'` when you need:
- Event handlers (onClick, onChange, etc.)
- State (useState, useReducer)
- Effects (useEffect, useLayoutEffect)
- Browser APIs (localStorage, window, navigator)
- Custom hooks that use above
- Interactive UI (modals, dropdowns, forms with validation)
### Example
```tsx
// app/components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
Count: {count}
);
}
```
### Composing Server and Client
```tsx
// app/page.tsx (Server Component)
import { Counter } from './components/Counter'; // Client Component
export default async function Page() {
const data = await fetch('https://api.example.com/stats');
const stats = await data.json();
return (
Dashboard
Server-rendered stats: {stats.total}
{/* Client Component embedded in Server Component */}
;
}
```
## Server Actions
### Form Mutations
```tsx
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function createUser(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
// Validate
if (!name || !email) {
return { error: 'Name and email required' };
}
// Create user
await db.user.create({
data: { name, email }
});
// Revalidate cache
revalidatePath('/users');
return { success: true };
}
```
### Using Server Actions
```tsx
// app/users/page.tsx
import { createUser } from './actions';
export default function UsersPage() {
return (
);
}
```
### With Client Component
```tsx
// app/components/UserForm.tsx
'use client';
import { createUser } from '../actions';
import { useFormState } from 'react-dom';
export function UserForm() {
const [state, formAction] = useFormState(createUser, null);
return (
);
}
```
## Streaming and Suspense
### Streaming with Suspense
```tsx
// app/dashboard/page.tsx
import { Suspense } from 'react';
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 3000));
return
Loaded after 3 seconds
;
}
export default function DashboardPage() {
return (
Dashboard
{/* Instant render */}
This loads immediately
{/* Streamed when ready */}
Loading...
}>
);
}
```
### Loading States
```tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
);
}
```
## Data Fetching Patterns
### Caching Strategies
```tsx
// ✅ GOOD: Cached (default)
const data = await fetch('https://api.example.com/data');
// ✅ GOOD: Revalidate every 1 hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
});
// ✅ GOOD: No caching (always fresh)
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
});
// ✅ GOOD: Tagged cache (revalidate by tag)
const data = await fetch('https://api.example.com/data', {
next: { tags: ['users'] }
});
// Revalidate specific tag
import { revalidateTag } from 'next/cache';
revalidateTag('users');
```
## Route Handlers (API Routes)
```tsx
// app/api/users/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const users = await db.user.findMany();
return NextResponse.json(users);
}
export async function POST(request: Request) {
const body = await request.json();
const user = await db.user.create({
data: body
});
return NextResponse.json(user, { status: 201 });
}
```
### Dynamic Route Handlers
```tsx
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const user = await db.user.findUnique({
where: { id: params.id }
});
if (!user) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
}
return NextResponse.json(user);
}
```
## Middleware
```tsx
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Check authentication
const token = request.cookies.get('token');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
};
```
## Anti-Patterns
```tsx
// ❌ BAD: Client Component doing data fetching
'use client';
export default function Page() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data').then(r => r.json()).then(setData);
}, []);
}
// ✅ GOOD: Server Component fetches data
export default async function Page() {
const data = await fetch('/api/data').then(r => r.json());
return
{data}
;
}
// ❌ BAD: Entire page as Client Component
'use client';
export default function Page() {
return
...
;
}
// ✅ GOOD: Only interactive parts as Client
export default function Page() {
return (
{/* This is 'use client' */}
);
}
// ❌ BAD: Passing Server Component to Client Component
// ✅ GOOD: Pass as children prop
{children} {/* Server Component passed as children */}
```
## Verification Before Deployment
- [ ] Server Components used by default
- [ ] `'use client'` only when necessary
- [ ] Data fetching in Server Components
- [ ] Server Actions for mutations
- [ ] Streaming with Suspense boundaries
- [ ] Caching strategy configured
- [ ] Middleware for auth/redirects
- [ ] Loading and error states
- [ ] Route handlers for APIs
- [ ] No unnecessary client bundles
## Integration with Project Standards
Enforces Next.js best practices:
- Performance optimization (streaming, caching)
- Security (Server Components hide secrets)
- Type safety (TypeScript throughout)
- No unnecessary client JavaScript
## Resources
- Next.js 15 Docs: https://nextjs.org/docs
- App Router: https://nextjs.org/docs/app
- Server Components: https://nextjs.org/docs/app/building-your-application/rendering/server-components
- Data Fetching: https://nextjs.org/docs/app/building-your-application/data-fetching
---
**Last Updated:** January 24, 2026
**Compatibility:** Claude Opus 4.5, Claude Code v2.x
**Status:** Production Ready
> **January 2026 Update:** This skill is compatible with Claude Opus 4.5 and Claude Code v2.x. For complex tasks, use the `effort: high` parameter for thorough analysis.