Feature-Sliced Design (FSD) v2.1
Source: fsd.how | Strictness can be adjusted based on project scale and team context.
1. Core Philosophy & Layer Overview
FSD v2.1 core principle: "Start simple, extract when needed."
Place code in pages/ first. Duplication across pages is acceptable and does
not automatically require extraction to a lower layer. Extract only when the
same code is currently being used in multiple places (not hypothetically),
the usages do not always change together, and the boundary has a focused
responsibility.
Not all layers are required. Most projects can start with only shared/,
pages/, and app/. Add widgets/, features/, entities/ only when they
provide clear value. Do not create empty layer folders "just in case."
FSD uses 6 standardized layers, listed here from highest to lowest:
app/ → App initialization, providers, routing
pages/ → Route-level composition, owns its own logic
widgets/ → Large composite UI blocks reused across multiple pages
features/ → Reusable user interactions (only when used in 2+ places)
entities/ → Reusable business domain models (only when used in 2+ places)
shared/ → Infrastructure with no business logic (UI kit, utils, API client)
Import rule: A module may only import from layers strictly below it. Cross-imports between slices on the same layer are forbidden.
// ✅ Allowed
import { Button } from "@/shared/ui/Button"; // features → shared
import { useUser } from "@/entities/user"; // pages → entities
// ❌ Violation
import { loginUser } from "@/features/auth"; // entities → features
import { likePost } from "@/features/like-post"; // features → features
Note: The processes/ layer is deprecated in v2.1. For migration
details, read references/migration-guide.md.
2. Decision Framework
When writing new code, follow this tree:
Step 1: Where is this code used?
- Used in only one page → keep it in that
pages/slice. - Used in 2+ pages but duplication is manageable → keeping separate copies in each page is also valid.
- An entity or feature used in only one page → keep it in that page
(Steiger:
insignificant-slice).
Step 2: Is it reusable infrastructure with no business logic?
- UI components →
shared/ui/ - Utility functions →
shared/lib/ - API client, route constants →
shared/api/orshared/config/ - Auth tokens, session management →
shared/auth/ - CRUD operations →
shared/api/
Step 3: Is it a complete user action currently used in multiple places, with stable boundaries?
- Yes →
features/ - Uncertain, single use, or speculative reuse → keep in the page.
Step 4: Is it a business domain model currently used in multiple places, with stable boundaries?
- Yes →
entities/ - Uncertain, single use, or speculative reuse → keep in the page.
Step 5: Is it app-wide configuration?
- Global providers, router, theme →
app/
Golden Rule: When in doubt, keep it in pages/. Extract only when the
same code is actively used in multiple places and the boundary is clear.
3. Quick Placement Table
| Scenario | Single use | Confirmed multi-use |
|---|---|---|
| User profile form | pages/profile/ui/ProfileForm.tsx | features/profile-form/ |
| Product card | pages/products/ui/ProductCard.tsx | entities/product/ui/ProductCard.tsx |
| Product data fetching | pages/product-detail/api/fetch-product.ts | entities/product/api/ |
| Auth token/session | shared/auth/ (always) | shared/auth/ (always) |
| Auth login form | pages/login/ui/LoginForm.tsx | features/auth/ |
| CRUD operations | shared/api/ (always) | shared/api/ (always) |
| Generic Card layout | shared/ui/Card/ | |
| Modal manager | shared/ui/modal-manager/ | |
| Modal content | pages/[page]/ui/SomeModal.tsx | |
| Date formatting util | shared/lib/format-date.ts |
4. Architectural Rules (MUST)
These rules are the foundation of FSD. Violations weaken the architecture. If you must break a rule, ensure it is an intentional design decision and document the reason in code (a comment or ADR).
4-1. Import only from lower layers
app → pages → widgets → features → entities → shared.
Upward imports and cross-imports between slices on the same layer are
forbidden.
4-2. Public API: every slice exports through index.ts
External consumers may only import from a slice's index.ts. Direct imports
of internal files are forbidden.
// ✅ Correct
import { LoginForm } from "@/features/auth";
// ❌ Violation: bypasses public API
import { LoginForm } from "@/features/auth/ui/LoginForm";
Shared layer: Shared has no slices. Define a separate public API per
segment (shared/ui/index.ts, shared/api/index.ts, etc.) rather than
one top-level shared/index.ts. This keeps imports from Shared
organized by intent.
RSC / meta-framework exception: Split entry points
(index.client.ts, index.server.ts) are permitted. Details and rules:
references/framework-integration.md.
4-3. No cross-imports between slices on the same layer
If two slices on the same layer need to share logic, follow the resolution order in Section 7. Do not create direct imports.
4-4. Domain-based file naming (no desegmentation)
Name files after the business domain they represent, not their technical role.
Technical-role names like types.ts, utils.ts, helpers.ts mix unrelated
domains in a single file and reduce cohesion.
// ❌ Technical-role naming
model/types.ts ← Which types? User? Order? Mixed?
model/utils.ts
// ✅ Domain-based naming
model/user.ts ← User types + related logic
model/order.ts ← Order types + related logic
api/fetch-profile.ts ← Clear purpose
4-5. No business logic in shared/
Shared contains only infrastructure: UI kit, utilities, API client setup,
route constants, assets. Business calculations, domain rules, and workflows
belong in entities/ or higher layers.
// ❌ Business logic in shared
// shared/lib/userHelpers.ts
export const calculateUserReputation = (user) => { ... };
// ✅ Move to the owning domain
// entities/user/lib/reputation.ts
export const calculateUserReputation = (user) => { ... };
5. Recommendations (SHOULD)
5-1. Pages First: place code where it is used
Place code in pages/ first. Extract to lower layers only when truly needed.
Extraction is a design decision that affects the whole project, so the
threshold should be high.
What stays in pages:
- Large UI blocks used only in one page
- Page-specific forms, validation, data fetching, state management
- Page-specific business logic and API integrations
- Code that looks reusable but is simpler to keep local
Evolution pattern: Start with everything in pages/profile/. When the
same user data is being consumed by another page (not hypothetically),
extract the shared model to entities/user/. Keep page-specific API calls
and UI in the page.
5-2. Be conservative with entities
The entities layer is highly accessible (almost every other layer can import from it), so changes propagate widely.
- Start without entities.
shared/+pages/+app/is valid FSD. Thin-client apps rarely need entities. - Do not split slices prematurely. Keep code in pages. Extract to e