---
title: Next.js
description: Next.js App Router, Server Components, and full-stack patterns
---
The Next.js skill provides expertise in Next.js with App Router, Server Components, Server Actions, and full-stack development patterns.
## When Activated
- React applications with SSR/SSG
- Full-stack applications
- App Router patterns
- Working in `app/` directory
## Core Patterns
### App Router Structure
```
app/
├── layout.tsx # Root layout
├── page.tsx # Home page
├── loading.tsx # Loading UI
├── error.tsx # Error UI
├── api/
│ └── users/
│ └── route.ts # API route
└── users/
├── page.tsx # Users page
└── [id]/
└── page.tsx # User detail
```
### Server Components
```tsx
// app/users/page.tsx - Server Component (default)
async function UsersPage() {
const users = await db.users.findMany();
return (
{users.map(user => (
{user.name}
))}
);
}
```
### Client Components
```tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
);
}
```
### API Routes
```typescript
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET() {
const users = await db.users.findMany();
return NextResponse.json(users);
}
export async function POST(request: NextRequest) {
const data = await request.json();
const user = await db.users.create({ data });
return NextResponse.json(user, { status: 201 });
}
```
### Server Actions
```tsx
// app/actions.ts
'use server';
export async function createUser(formData: FormData) {
const name = formData.get('name') as string;
await db.users.create({ data: { name } });
revalidatePath('/users');
}
// app/users/new/page.tsx
import { createUser } from '@/app/actions';
export default function NewUserPage() {
return (
);
}
```
### Dynamic Routes
```tsx
// app/users/[id]/page.tsx
interface PageProps {
params: { id: string };
}
export default async function UserPage({ params }: PageProps) {
const user = await db.users.findUnique({
where: { id: params.id }
});
if (!user) {
notFound();
}
return
{user.name}
;
}
```
### Layouts
```tsx
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
## Best Practices
1. **Use Server Components by default**
2. **Add 'use client' only when needed**
3. **Colocate data fetching with components**
4. **Use loading.tsx for suspense boundaries**
5. **Implement proper error boundaries**
## Common Pitfalls
### Using Hooks in Server Components
```tsx
// ❌ BAD: Hooks in Server Component
export default async function Page() {
const [state, setState] = useState(0); // Error!
return
{state}
;
}
// ✅ GOOD: Mark as Client Component
'use client';
export default function Page() {
const [state, setState] = useState(0);
return
{state}
;
}
```
### Large Client Bundles
```tsx
// ❌ BAD: Entire page as Client Component
'use client';
export default function Page() {
const [count, setCount] = useState(0);
const data = await fetchData(); // Can't use await in Client Component
return (
);
}
// ✅ GOOD: Extract interactive parts only
export default async function Page() {
const data = await fetchData(); // Server Component can await
return (
{/* Server Component */}
{/* Small Client Component */}
);
}
// Counter.tsx
'use client';
export function Counter() {
const [count, setCount] = useState(0);
return ;
}
```
### Missing Loading States
```tsx
// ❌ BAD: No loading state
export default async function Page() {
const data = await slowFetch();
return