Feature-Sliced Design architecture for frontend. Use when creating new features, slices, or understanding the FSD layer structure.
The frontend uses Feature-Sliced Design. This skill documentation helps you understand the architecture and correctly create new features.
app/ ā widgets, features, entities, shared
widgets/ ā features, entities, shared
features/ ā entities, shared
entities/ ā shared
shared/ ā (only external libs)
Import direction: ONLY downward!
frontend/src/
āāā app/ # Next.js App Router (outside FSD)
ā
āāā widgets/ # Composite UI Blocks
ā āāā header/
ā āāā ui/
ā ā āāā header.tsx
ā ā āāā mode-toggle.tsx
ā āāā index.ts # Public API
ā
āāā features/ # User Interactions / Use Cases
ā āāā auth/
ā ā āāā ui/
ā ā ā āāā login-form.tsx
ā ā ā āāā register-form.tsx
ā ā āāā model/
ā ā ā āāā use-auth-sync.ts
ā ā āāā index.ts
ā āāā stats/
ā āāā ui/
ā ā āāā stats-grid.tsx
ā āāā model/
ā ā āāā use-sse.ts
ā āāā index.ts
ā
āāā entities/ # Business Objects
ā āāā user/
ā āāā ui/
ā ā āāā user-info.tsx
ā āāā model/
ā ā āāā types.ts
ā āāā index.ts
ā
āāā shared/ # Reusable Code
āāā ui/ # shadcn/ui components
āāā api/ # Orval-generated
ā āāā endpoints/
ā āāā models/
ā āāā custom-fetch.ts
āāā lib/
ā āāā auth-client/ # Client-safe Auth
ā āāā auth-server/ # Server-only Auth
ā āāā query-client.ts
ā āāā utils.ts
āāā config/
āāā providers.tsx
āāā theme-provider.tsx
// Always use layer aliases:
import { Button } from "@shared/ui/button"
import { useAuthSync } from "@features/auth"
import { SessionUser } from "@entities/user"
import { Header } from "@widgets/header"
Each slice can have these segments:
| Segment | Purpose | Example |
|---|---|---|
ui/ |
React Components | login-form.tsx |
model/ |
Hooks, State, Types | use-auth-sync.ts |
api/ |
API Calls (rare, mostly in shared) | user-api.ts |
lib/ |
Utilities for the slice | validation.ts |
Every slice MUST have an index.ts:
// features/auth/index.ts
export { LoginForm } from "./ui/login-form"
export { RegisterForm } from "./ui/register-form"
export { useAuthSync, broadcastSignOut } from "./model/use-auth-sync"
Always import via index.ts:
// ā
Correct
import { LoginForm } from "@features/auth"
// ā Wrong (Public API Sidestep)
import { LoginForm } from "@features/auth/ui/login-form"
mkdir -p src/features/<name>/ui
mkdir -p src/features/<name>/model # if hooks/state needed
// src/features/<name>/ui/<name>-form.tsx
"use client"
import { Button } from "@shared/ui/button"
import { Card } from "@shared/ui/card"
export function NameForm() {
return (
<Card>
<Button>Action</Button>
</Card>
)
}
// src/features/<name>/model/use-<name>.ts
"use client"
import { useQueryClient } from "@tanstack/react-query"
import { useCallback } from "react"
export function useName() {
const queryClient = useQueryClient()
// ...
return { /* ... */ }
}
// src/features/<name>/index.ts
export { NameForm } from "./ui/name-form"
export { useName } from "./model/use-name"
// app/(protected)/page.tsx
import { NameForm } from "@features/<name>"
export default function Page() {
return <NameForm />
}
// src/entities/<name>/model/types.ts
export interface Product {
id: string
name: string
price: number
}
// src/entities/<name>/ui/product-card.tsx
"use client"
import type { Product } from "../model/types"
import { Card } from "@shared/ui/card"
export function ProductCard({ product }: { product: Product }) {
return <Card>{product.name}</Card>
}
// src/entities/<name>/index.ts
export type { Product } from "./model/types"
export { ProductCard } from "./ui/product-card"
// ā
ALLOWED
// In app/:
import { Header } from "@widgets/header"
import { LoginForm } from "@features/auth"
import { SessionUser } from "@entities/user"
import { Button } from "@shared/ui/button"
// In widgets/:
import { useAuthSync } from "@features/auth"
import { SessionUser } from "@entities/user"
import { Button } from "@shared/ui/button"
// In features/:
import { SessionUser } from "@entities/user"
import { Button } from "@shared/ui/button"
// In entities/:
import { Button } from "@shared/ui/button"
// ā FORBIDDEN
// In shared/ NEVER import features/!
// In entities/ NEVER import features/!
// In features/ NEVER import other features/!
FSD rules are automatically checked:
# Integrated in lint
bun run lint
# Only Steiger
bunx steiger src
| Error | Cause | Solution |
|---|---|---|
no-public-api-sidestep |
Direct import instead of via index.ts | Import via index.ts |
insignificant-slice |
Slice has only 1 reference | Use more or move to widget/higher layer |
forbidden-imports |
Import from higher layer | Fix import direction |
// Server Component (no "use client")
// ā Can only import shared/, no hooks
// Client Component
"use client"
// ā Can use hooks, but NEVER server-only code
// In Client Components:
import { signIn, signOut } from "@shared/lib/auth-client"
// In Server Components:
import { getSession } from "@shared/lib/auth-server"
bun run lint checks FSD rules