);
}
```
## Server Components vs Client Components
### Decision Rule
| Need | Component Type |
|-------------------------------------------|---------------|
| Fetch data, access backend resources | Server (default) |
| Static rendering, SEO content | Server |
| Use hooks (useState, useEffect, etc.) | Client |
| Browser APIs (window, localStorage) | Client |
| Event handlers (onClick, onChange) | Client |
| Third-party client-only libraries | Client |
### Server Component (Default)
All components in the `app/` directory are Server Components by default. They run on the server only and can directly access databases, file systems, and secrets.
```tsx
// app/users/page.tsx -- Server Component (no directive needed)
import { db } from '@/lib/db';
export default async function UsersPage() {
const users = await db.user.findMany();
return (
{users.map((user) =>
{user.name}
)}
);
}
```
### Client Component
Add `'use client'` at the top of the file. Push this directive as low in the tree as possible.
```tsx
// components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return ;
}
```
### Composition Pattern
Fetch data in Server Components, pass to Client Components as props:
```tsx
// app/dashboard/page.tsx (Server Component)
import { ClientSidebar } from '@/components/ClientSidebar';
import { db } from '@/lib/db';
export default async function Dashboard() {
const stats = await db.stats.get();
return (
);
}
```
## Data Fetching
### Server Component Fetch with Caching
```tsx
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }, // ISR: revalidate every hour
});
if (!res.ok) throw new Error('Failed to fetch products');
return res.json();
}
```
### Fetch Caching Options
| Option | Behavior |
|---------------------------------|-----------------------------------|
| `{ cache: 'force-cache' }` | Static (default for GET) |
| `{ cache: 'no-store' }` | Dynamic (no caching) |
| `{ next: { revalidate: N } }` | ISR (revalidate every N seconds) |
| `{ next: { tags: ['posts'] } }`| Tag-based revalidation |
### Parallel Fetching
Always fetch independent data in parallel with `Promise.all`:
```tsx
export default async function Dashboard({ params }: { params: { id: string } }) {
const [user, orders] = await Promise.all([
getUser(params.id),
getOrders(params.id),
]);
return
;
}
```
### Streaming with Suspense
```tsx
import { Suspense } from 'react';
export default function Dashboard() {
return (
}>
}>
);
}
```
## Server Actions
Define mutations with `'use server'`. They run on the server and can be called from forms or client code.
```tsx
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { z } from 'zod';
const createPostSchema = z.object({
title: z.string().min(1),
content: z.string().min(10),
});
export async function createPost(formData: FormData) {
const validated = createPostSchema.parse({
title: formData.get('title'),
content: formData.get('content'),
});
await db.post.create({ data: validated });
revalidatePath('/posts');
redirect(`/posts`);
}
```
Use in a form (no client JavaScript required for basic submissions):
```tsx
export default function NewPost() {
return (
);
}
```
## API Route Handlers
```tsx
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
export async function GET(request: NextRequest) {
const page = parseInt(request.nextUrl.searchParams.get('page') || '1');
const users = await db.user.findMany({ skip: (page - 1) * 10, take: 10 });
return NextResponse.json(users);
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const validated = userSchema.parse(body);
const user = await db.user.create({ data: validated });
return NextResponse.json(user, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json({ errors: error.errors }, { status: 400 });
}
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
```
## Middleware
Runs at the edge before every matched request. Use for auth checks, redirects, headers.
```tsx
// middleware.ts (project root)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('session')?.value;
const isProtected = request.nextUrl.pathname.startsWith('/dashboard');
if (isProtected && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};
```
## Error Handling
### Error Boundary (`error.tsx`)
```tsx
// app/dashboard/error.tsx
'use client';
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
Something went wrong
);
}
```
### Not Found
```tsx
// app/not-found.tsx
import Link from 'next/link';
export default function NotFound() {
return (
Not Found
Return Home
);
}
```
Trigger programmatically: `import { notFound } from 'next/navigation'; notFound();`
## Configuration
```js
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [{ protocol: 'https', hostname: '**.example.com' }],
},
async redirects() {
return [{ source: '/old', destination: '/new', permanent: true }];
},
async headers() {
return [{
source: '/api/:path*',
headers: [{ key: 'Access-Control-Allow-Origin', value: '*' }],
}];
},
};
module.exports = nextConfig;
```
## Guardrails
- Use Server Components by default; add `'use client'` only when needed
- Push `'use client'` as low in the component tree as possible
- Colocate data fetching with the component that needs it
- Use `Promise.all` for independent parallel fetches
- Implement `loading.tsx` and `error.tsx` for every route segment
- Use Server Actions for mutations (not API routes for form submissions)
- Validate all inputs with schema validators (Zod) in Server Actions and API routes
- Use `next/image` for images and `next/font` for fonts (performance)
- Set proper metadata on every page for SEO
- Use Suspense boundaries to stream slow data
- Never import server-only modules in Client Components
- Never expose secrets or database access in Client Components
## Commands Reference
```bash
npm run dev # Development server (http://localhost:3000)
npm run build # Production build
npm run start # Start production server
npm run lint # ESLint check
npx tsc --noEmit # TypeScript validation
npm test # Run tests (Vitest/Jest)
```
## Advanced Topics
For detailed code examples, advanced patterns, testing strategies, performance optimization, caching strategies, and ISR/SSG/SSR details, see:
- [references/patterns.md](references/patterns.md) -- Authentication, advanced Server Actions, testing, performance, caching, rendering strategies, deployment
## External References
- [Next.js Documentation](https://nextjs.org/docs)
- [Next.js App Router](https://nextjs.org/docs/app)
- [NextAuth.js / Auth.js](https://authjs.dev/)
- [Vercel Deployment](https://vercel.com/docs)
- [React Server Components](https://react.dev/reference/rsc/server-components)