Next.js Development Patterns
When to Use This Skill
- Building Next.js applications with App Router
- Implementing Server Components and Server Actions
- Creating full-stack React applications
- SEO-optimized web applications
- Following Next.js 14+ best practices
Target Agents
nextjs-expert - Primary user for Next.js development
react-typescript-expert - TypeScript React patterns
frontend-developer - Full-stack web development
Core Patterns
1. App Router Structure
app/
├── (marketing)/
│ ├── page.tsx # /
│ └── about/
│ └── page.tsx # /about
├── (dashboard)/
│ ├── layout.tsx
│ └── dashboard/
│ └── page.tsx # /dashboard
├── api/
│ └── users/
│ └── route.ts # API route
├── layout.tsx # Root layout
└── page.tsx # Home page
2. Server Components (Default)
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // ISR: revalidate every hour
})
return res.json()
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<div>
<h1>Posts</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}
3. Client Components
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<button => setCount(count + 1)}>
Count: {count}
</button>
)
}
4. Server Actions
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
await db.post.create({
data: { title, content }
})
revalidatePath('/posts')
return { success: true }
}
// app/posts/new/page.tsx
import { createPost } from '@/app/actions'
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="content" required />
<button type="submit">Create Post</button>
</form>
)
}
5. Dynamic Routes & Params
// app/posts/[id]/page.tsx
interface PageProps {
params: { id: string }
searchParams: { [key: string]: string | string[] | undefined }
}
export async function generateMetadata({ params }: PageProps) {
const post = await getPost(params.id)
return { title: post.title }
}
export default async function PostPage({ params }: PageProps) {
const post = await getPost(params.id)
return <article>{/* post content */}</article>
}
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ id: post.id }))
}
6. API Routes
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const users = await db.user.findMany()
return NextResponse.json(users)
}
export async function POST(request: NextRequest) {
const body = await request.json()
const user = await db.user.create({ data: body })
return NextResponse.json(user, { status: 201 })
}
7. Middleware
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
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*']
}
8. Data Fetching Patterns
// Parallel Data Fetching
async function getData() {
const [posts, users] = await Promise.all([
fetch('https://api.example.com/posts'),
fetch('https://api.example.com/users')
])
return {
posts: await posts.json(),
users: await users.json()
}
}
// Sequential (when dependent)
async function getPostAndComments(id: string) {
const post = await getPost(id)
const comments = await getComments(post.id)
return { post, comments }
}
9. Streaming & Suspense
import { Suspense } from 'react'
export default function Page() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<LoadingSkeleton />}>
<Posts />
</Suspense>
<Suspense fallback={<LoadingSkeleton />}>
<Analytics />
</Suspense>
</div>
)
}
10. Image Optimization
import Image from 'next/image'
export default function Profile() {
return (
<Image
src="/profile.jpg"
alt="Profile"
width={500}
height={500}
priority
quality={90}
/>
)
}
Best Practices
- Use Server Components by default
- Add
'use client' only when needed (interactivity)
- Leverage Server Actions for mutations
- Implement proper caching strategies
- Use Suspense for streaming
- Optimize images with
next/image
- Implement proper SEO metadata
- Use TypeScript for type safety
- Follow App Router conventions
- Implement error boundaries
Additional Resources
- caching.md - Caching strategies
- authentication.md - Auth patterns
- deployment.md - Deployment best practices