nextjs-guidelinesNext.js App Router best practices ā Server Components, data fetching, caching, routing, middleware, metadata, error handling, streaming, Server Actions, and performance optimization for Next.js 14-16+.
Install via ClawdBot CLI:
clawdbot install wpank/nextjs-guidelinesApply these patterns when building, reviewing, or debugging Next.js App Router applications.
npx clawhub@latest install nextjs
| Mode | Where | When to Use |
|------|-------|-------------|
| Server Components | Server only | Data fetching, secrets, heavy computation |
| Client Components | Browser | Interactivity, hooks, browser APIs |
| Static (SSG) | Build time | Content that rarely changes |
| Dynamic (SSR) | Request time | Personalized or real-time data |
| Streaming | Progressive | Large pages, slow data sources |
Does it need...?
āāā useState, useEffect, event handlers, browser APIs
ā āāā Client Component ('use client')
āāā Direct data fetching, no interactivity
ā āāā Server Component (default)
āāā Both?
āāā Split: Server parent fetches data ā Client child handles UI
See file-conventions.md for complete reference.
app/
āāā layout.tsx # Shared UI wrapper (persists across navigations)
āāā page.tsx # Route UI
āāā loading.tsx # Suspense fallback (automatic)
āāā error.tsx # Error boundary (must be 'use client')
āāā not-found.tsx # 404 UI
āāā route.ts # API endpoint (cannot coexist with page.tsx)
āāā template.tsx # Like layout but re-mounts on navigation
āāā default.tsx # Parallel route fallback
āāā opengraph-image.tsx # OG image generation
Route segments: [slug] dynamic, [...slug] catch-all, [[...slug]] optional catch-all, (group) route group, @slot parallel route, _folder private (excluded from routing).
Choose the right pattern for each use case. See data-patterns.md for full decision tree.
| Pattern | Use Case | Caching |
|---------|----------|---------|
| Server Component fetch | Internal reads (preferred) | Full Next.js caching |
| Server Action | Mutations, form submissions | POST only, no cache |
| Route Handler | External APIs, webhooks, public REST | GET can be cached |
| Client fetch ā API | Client-side reads (last resort) | HTTP cache headers |
// app/products/page.tsx ā Server Component by default
export default async function ProductsPage() {
const products = await db.product.findMany() // Direct DB access, no API layer
return <ProductGrid products={products} />
}
// BAD: Sequential ā each awaits before the next starts
const user = await getUser()
const posts = await getPosts()
// GOOD: Parallel fetching
const [user, posts] = await Promise.all([getUser(), getPosts()])
// GOOD: Streaming with Suspense ā each section loads independently
<Suspense fallback={<UserSkeleton />}><UserSection /></Suspense>
<Suspense fallback={<PostsSkeleton />}><PostsSection /></Suspense>
// app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export async function addToCart(productId: string) {
const cookieStore = await cookies()
const sessionId = cookieStore.get('session')?.value
if (!sessionId) redirect('/login')
await db.cart.upsert({
where: { sessionId_productId: { sessionId, productId } },
update: { quantity: { increment: 1 } },
create: { sessionId, productId, quantity: 1 },
})
revalidateTag('cart')
return { success: true }
}
| Method | Syntax | Use Case |
|--------|--------|----------|
| No cache | fetch(url, { cache: 'no-store' }) | Always-fresh data |
| Static | fetch(url, { cache: 'force-cache' }) | Rarely changes |
| ISR | fetch(url, { next: { revalidate: 60 } }) | Time-based refresh |
| Tag-based | fetch(url, { next: { tags: ['products'] } }) | On-demand invalidation |
Invalidate from Server Actions:
'use server'
import { revalidateTag, revalidatePath } from 'next/cache'
export async function updateProduct(id: string, data: ProductData) {
await db.product.update({ where: { id }, data })
revalidateTag('products') // Invalidate by tag
revalidatePath('/products') // Invalidate by path
}
Props crossing Server ā Client boundary must be JSON-serializable. See rsc-boundaries.md.
| Prop Type | Valid? | Fix |
|-----------|--------|-----|
| string, number, boolean | Yes | ā |
| Plain object / array | Yes | ā |
| Server Action ('use server') | Yes | ā |
| Function () => {} | No | Define inside client component |
| Date object | No | Use .toISOString() |
| Map, Set, class instance | No | Convert to plain object/array |
Critical rule: Client Components cannot be async. Fetch data in a Server Component parent and pass it down.
params, searchParams, cookies(), and headers() are all async. See async-patterns.md.
// Pages and layouts ā always await params
type Props = { params: Promise<{ slug: string }> }
export default async function Page({ params }: Props) {
const { slug } = await params
}
// Server functions
const cookieStore = await cookies()
const headersList = await headers()
// Non-async components ā use React.use()
import { use } from 'react'
export default function Page({ params }: Props) {
const { slug } = use(params)
}
| Pattern | Syntax | Purpose |
|---------|--------|---------|
| Route groups | (marketing)/ | Organize without affecting URL |
| Parallel routes | @analytics/ | Multiple independent sections in one layout |
| Intercepting routes | (.)photos/[id] | Modal overlays on soft navigation |
| Private folders | _components/ | Exclude from routing |
See parallel-routes.md for complete modal pattern.
Key rules:
@slot folder must have a default.tsx (returns null) or you get 404 on refreshrouter.back(), never router.push() or (.) same level, (..) one level up, (...) from rootSee metadata.md for OG images, sitemaps, and file conventions.
// Static metadata (layout or page)
export const metadata: Metadata = {
title: { default: 'My App', template: '%s | My App' },
description: 'Built with Next.js',
}
// Dynamic metadata
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title,
description: post.description,
openGraph: { images: [{ url: post.image, width: 1200, height: 630 }] },
}
}
Metadata is Server Components only. If a page has 'use client', extract metadata to a parent layout.
See error-handling.md for full patterns including auth errors.
// app/blog/error.tsx ā must be 'use client'
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
Critical gotcha: redirect(), notFound(), forbidden(), and unauthorized() throw special errors. Never catch them in try/catch:
// BAD: redirect throw is caught ā navigation fails!
try {
await db.post.create({ data })
redirect(`/posts/${post.id}`)
} catch (error) {
return { error: 'Failed' } // Catches the redirect too!
}
// GOOD: Call redirect outside try-catch
let post
try { post = await db.post.create({ data }) }
catch (error) { return { error: 'Failed' } }
redirect(`/posts/${post.id}`)
export default async function ProductPage({ params }: Props) {
const { id } = await params
const product = await getProduct(id) // Blocking ā loads first
return (
<div>
<ProductHeader product={product} />
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={id} /> {/* Streams in independently */}
</Suspense>
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations productId={id} /> {/* Streams in independently */}
</Suspense>
</div>
)
}
| Hook | Suspense Required |
|------|-------------------|
| useSearchParams() | Always (or entire page becomes CSR) |
| usePathname() | In dynamic routes |
| useParams() | No |
| useRouter() | No |
next/image over ![]()
ā see image-optimization.mdnext/link over ā client-side navigation with prefetchingnext/font ā see font-optimization.mdnext/script ā see scripts.mdpriority on above-the-fold images (LCP)sizes when using fill ā without it, the largest image variant downloadsconst Chart = dynamic(() => import('./Chart'))generateStaticParams to pre-render dynamic routes at build timeSee route-handlers.md for API endpoint patterns.
See bundling.md for fixing third-party package issues, server-incompatible packages, and ESM/CommonJS problems.
See hydration-errors.md for all causes and fixes.
| Cause | Fix |
|-------|-----|
| Browser APIs (window, localStorage) | Client component with useEffect mount check |
| new Date().toLocaleString() | Render on client with useEffect |
| Math.random() for IDs | Use useId() hook |
|
| Third-party scripts modifying DOM | Use next/script with afterInteractive |
See self-hosting.md for Docker, PM2, cache handlers, and deployment checklist.
Key points:
output: 'standalone' for Docker ā creates minimal production bundlepublic/ and .next/static/ separately (not included in standalone)HOSTNAME="0.0.0.0" for containers/api/health| Never | Why | Instead |
|-------|-----|---------|
| Add 'use client' by default | Bloats client bundle, loses Server Component benefits | Server Components are default ā add 'use client' only for interactivity |
| Make client components async | Not supported ā will crash | Fetch in Server Component parent, pass data as props |
| Pass Date/Map/functions to client | Not serializable across RSC boundary | Serialize to string/plain object, or use Server Actions |
| Fetch from own API in Server Components | Unnecessary round-trip ā you're already on the server | Access DB/service directly |
| Wrap redirect()/notFound() in try-catch | They throw special errors that get swallowed | Call outside try-catch or use unstable_rethrow() |
| Skip loading.tsx or Suspense fallbacks | Users see blank page during data loading | Always provide loading states |
| Use useSearchParams without Suspense | Entire page silently falls back to CSR | Wrap in boundary |
| Use router.push() to close modals | Breaks history, modal can flash/persist | Use router.back() |
| Use @vercel/og for OG images | Built into Next.js already | Import from next/og |
| Omit default.tsx in parallel route slots | Hard navigation (refresh) returns 404 | Add default.tsx returning null |
| Use Edge runtime unless required | Limited APIs, most npm packages break | Default Node.js runtime covers 95% of cases |
| Skip sizes prop on fill images | Downloads largest image variant always | Add sizes="100vw" or appropriate breakpoints |
| Import fonts in multiple components | Creates duplicate instances | Import once in layout, use CSS variable |
| Use for Google Fonts | No optimization, blocks rendering | Use next/font |
| File | Topic |
|------|-------|
| rsc-boundaries.md | Server/Client boundary rules, serialization |
| data-patterns.md | Fetching decision tree, waterfall avoidance |
| error-handling.md | Error boundaries, redirect gotcha, auth errors |
| async-patterns.md | Next.js 15+ async params/cookies/headers |
| metadata.md | SEO, OG images, sitemaps, file conventions |
| parallel-routes.md | Modal pattern, intercepting routes, gotchas |
| hydration-errors.md | Causes, debugging, fixes |
| self-hosting.md | Docker, PM2, cache handlers, deployment |
| file-conventions.md | Project structure, special files, middleware |
| bundling.md | Third-party packages, SSR issues, Turbopack |
| image-optimization.md | next/image best practices |
| font-optimization.md | next/font best practices |
| scripts.md | next/script, third-party loading |
| route-handlers.md | API endpoints, request/response helpers |
Generated Mar 1, 2026
Building a scalable online store with dynamic product listings, user authentication, and shopping cart functionality using Server Components for data fetching and Server Actions for cart updates. Optimize performance with ISR for product pages and streaming for personalized recommendations.
Creating a headless CMS with real-time content updates, SEO-friendly metadata, and image optimization. Use parallel routes for admin dashboards and intercepting routes for preview modes, while caching static content and invalidating on edits via tag-based revalidation.
Developing a data-intensive dashboard with interactive charts and real-time metrics using Client Components for UI interactivity and Server Components for secure data aggregation. Implement streaming to handle slow data sources and error boundaries for robust error handling.
Building a learning platform with course pages, user progress tracking, and video streaming. Utilize Server Actions for enrollment and progress updates, middleware for authentication, and static generation for course catalogs to reduce load times.
Designing a secure appointment booking system with patient portals and doctor schedules. Leverage Server Components for data privacy, route handlers for external API integrations, and dynamic rendering for real-time availability updates.
Offer tiered subscription plans for access to premium features like advanced analytics or custom integrations. Use Server Actions to handle billing updates and caching to optimize performance for recurring users.
Generate revenue through transaction fees, seller subscriptions, and advertising. Implement Server Components for product listings and Server Actions for order processing, with caching strategies to handle high traffic during sales.
Monetize via paywalls, sponsored content, and affiliate marketing. Use streaming for large media files and metadata optimization for SEO to drive traffic, while Server Actions manage user subscriptions and content access.
š¬ Integration Tip
Integrate with existing databases or APIs using Server Components for secure data access and route handlers for external endpoints, ensuring props are JSON-serializable across RSC boundaries.
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
Provides a 7-step debugging protocol plus language-specific commands to systematically identify, verify, and fix software bugs across multiple environments.
A comprehensive skill for using the Cursor CLI agent for various software engineering tasks (updated for 2026 features, includes tmux automation guide).
Write, run, and manage unit, integration, and E2E tests across TypeScript, Python, and Swift using recommended frameworks.
Control and operate Opencode via slash commands. Use this skill to manage sessions, select models, switch agents (plan/build), and coordinate coding through Opencode.
Coding style memory that adapts to your preferences, conventions, and patterns for consistent coding.