Next.js App Router Patterns
For latest Next.js APIs, use context7.
Package manager: Use bun for all commands.
Project Structure (Feature-Sliced Design)
For Feature-Sliced Design use Context7.
app/ # Next.js App Router (file-based routing)
├── layout.tsx # Root layout
├── page.tsx # Home (/)
└── some-page/
└── page.tsx # /some-page (routing + page composition)
src/
├── app/ # App-wide settings, providers, global styles
│ └── providers/
├── widgets/ # Large composite blocks (Header, Sidebar, Feed)
├── features/ # User interactions (auth, send-comment, add-to-cart)
│ └── auth/
│ ├── ui/
│ ├── model/
│ ├── api/
│ └── actions/ # Server Actions
├── entities/ # Business entities (user, product, order)
│ └── user/
│ ├── ui/
│ ├── model/
│ └── api/
└── shared/ # Reusable infrastructure
├── ui/ # Design system
├── lib/ # Utilities, helpers
├── api/ # API client
└── config/ # Environment, constants
FSD Layer Rules
| Layer | Can import from | Cannot import from |
|---|---|---|
app | All layers below | - |
widgets | features, entities, shared | app |
features | entities, shared | app, widgets |
entities | shared | app, widgets, features |
shared | - | All layers above |
Rule: Layers can only import from layers below. Never above.
Server vs Client Components
Need useState, useEffect, onClick? → 'use client' Need browser APIs (window, localStorage)? → 'use client' Everything else → Server Component (default)
Rule: Server Components by default. Add 'use client' only when needed.
Composition Pattern
// Server Component fetches, Client Component interacts
async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await getProduct(id); // Server-side fetch
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<AddToCartButton productId={product.id} /> {/* Client leaf */}
</div>
);
}
Streaming with Suspense
Rule: Don't block on slow data. Stream progressively.
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1> {/* Renders immediately */}
<Suspense fallback={<StatsSkeleton />}>
<StatsCards /> {/* Streams when ready */}
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart /> {/* Streams when ready */}
</Suspense>
</div>
);
}
State Management
| State Type | Solution |
|---|---|
| Server data in Server Component | Direct fetch (no library) |
| Server data in Client Component | TanStack Query |
| Form input | useState |
| Global client (theme, cart) | Zustand + persist |
Headless Patterns
Rule: Separate WHAT (logic) from HOW (presentation).
// ❌ Before: Logic mixed in component
const ProductsPage = () => {
const [products, setProducts] = useState([]);
const [filters, setFilters] = useState({});
const [isLoading, setIsLoading] = useState(true);
// ... 300+ lines
};
// ✅ After: Main component is composition only
const ProductsPage = () => {
const { filters, setFilter, clear } = useProductFilters();
const { products, isLoading } = useProducts(filters);
return (
<Page>
<FilterBar filters={filters} onChange={setFilter} onClear={clear} />
<Suspense fallback={<ProductsSkeleton />}>
<ProductGrid products={products} isLoading={isLoading} />
</Suspense>
</Page>
);
};
Rule: Main component does composition only. Logic goes in hooks.
Server Actions
For latest Server Actions API, use context7 MCP or see Next.js Server Actions docs.
Rule: Server Actions go in features/*/actions/ directory.
Pattern:
- •
'use server'directive at top - •Zod schema for validation
- •Return
{ error }or{ success } - •Call
revalidatePathorrevalidateTagafter mutation
// Client form uses useActionState const [state, dispatch, isPending] = useActionState(serverAction, initialState);
See examples.md for full implementation.
Data Caching
For latest caching APIs, use context7 MCP or see Next.js Caching docs.
| Pattern | Use for |
|---|---|
cache() from React | Request deduplication (same render) |
unstable_cache() | Cross-request caching with revalidation |
revalidatePath() | Invalidate specific path |
revalidateTag() | Invalidate by tag |
Rule: Always set revalidation strategy. Don't cache indefinitely.
Quick Checklist
Architecture
- • Using FSD layer rules (no upward imports)
- • Main component is composition only
- • Logic extracted to custom hooks
- • Server Actions in features/*/actions/
Server/Client
- • Server Components by default
- • 'use client' only when needed (useState, onClick, browser APIs)
- • Client components are leaf nodes
Data
- • Server data fetched in Server Components
- • Slow data wrapped in Suspense
- • Forms use Server Actions + useActionState
- • Proper cache invalidation (revalidatePath/revalidateTag)
Design System
- • Using tokens from design-system (no hardcoded values)
- • Proper loading/error states
Security Configuration
| Header | Value |
|---|---|
| HSTS | max-age=63072000; includeSubDomains; preload |
| X-Frame-Options | SAMEORIGIN |
| X-Content-Type-Options | nosniff |
| Referrer-Policy | strict-origin-when-cross-origin |
| Item | Value |
|---|---|
| Cookies httpOnly | true |
| Cookies secure | true |
| Cookies sameSite | strict |