| name | fsd-architecture |
| description | Feature-Sliced Design architecture for frontend. Use when creating new features, slices, or understanding the FSD layer structure. |
| allowed-tools | Read, Edit, Write, Glob, Grep |
Feature-Sliced Design (FSD) Architecture
The frontend uses Feature-Sliced Design. This skill documentation helps you understand the architecture and correctly create new features.
Layer Hierarchy (STRICT)
app/ → widgets, features, entities, shared
widgets/ → features, entities, shared
features/ → entities, shared
entities/ → shared
shared/ → (only external libs)
Import direction: ONLY downward!
Project Structure
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
TypeScript Path Aliases
// 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"
Slice Segments
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 |
Public API Pattern (IMPORTANT!)
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"
Creating a New Feature
1. Create folder structure
mkdir -p src/features/<name>/ui
mkdir -p src/features/<name>/model # if hooks/state needed
2. Create UI Component
// 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>
)
}
3. Create Model/Hook (optional)
// 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 { /* ... */ }
}
4. Create Public API
// src/features/<name>/index.ts
export { NameForm } from "./ui/name-form"
export { useName } from "./model/use-name"
5. Use in App
// app/(protected)/page.tsx
import { NameForm } from "@features/<name>"
export default function Page() {
return <NameForm />
}
Creating a New Entity
// 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"
Import Rules
// ✅ 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/!
Steiger Linting
FSD rules are automatically checked:
# Integrated in lint
bun run lint
# Only Steiger
bunx steiger src
Common Steiger Errors
| 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 vs Client Components
// Server Component (no "use client")
// → Can only import shared/, no hooks
// Client Component
"use client"
// → Can use hooks, but NEVER server-only code
Auth Separation
// In Client Components:
import { signIn, signOut } from "@shared/lib/auth-client"
// In Server Components:
import { getSession } from "@shared/lib/auth-server"
Summary
- Respect layers: Only import downward
- Public API: Always export/import via index.ts
- Segments: ui/, model/, api/, lib/ as needed
- Aliases: @shared/, @entities/, @features/, @widgets/
- Linting:
bun run lintchecks FSD rules