Suggest an editImprove this articleRefine the answer for “Feature-sliced design (FSD). must-know frontend architecture”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Feature-Sliced Design (FSD)** is a frontend architecture convention that groups code by domain feature and enforces strict top-down layer dependencies. ``` src/ app/ # routing, providers pages/ # page components features/ # user interactions (addToCart, login) entities/ # business domain (User, Product, Order) shared/ # generic utilities, UI kit ``` **Key rule:** each layer imports only from layers below it. One feature, one folder, one direction.Shown above the full answer for quick recall.Answer (EN)Image**Feature-Sliced Design (FSD)** is a frontend architecture convention that organizes code into strict horizontal layers (app, pages, features, entities, shared), where each layer can only import from layers below it, and all files for one domain live together in one slice. ## Theory ### TL;DR - Think of it like a company org chart: `app` is the CEO, `pages` are departments, `features` are teams delivering user actions, `entities` are core business objects, `shared` is office supplies. - Main difference from classic structure: code groups by feature/domain, not by file type. No giant `components/` folder. - Dependency rule: `shared` → `entities` → `features` → `pages` → `app`. Only bottom-up. Never the other way. - Use FSD for teams of 5+ developers or when business requirements change often. For prototypes under 1000 lines, skip it. - No compiler magic. Convention enforced by ESLint plugins. ### Quick example ``` // Traditional "by-type" structure (breaks down at scale): src/ components/ # CartButton, UserCard, ProductList all mixed pages/ cart.tsx # imports from components/, utils/, api/ utils/ api/ // FSD structure (everything co-located by domain): src/ app/ # routing, providers pages/cart/ ui/CartButton.tsx # cart UI lives here model/cartApi.ts # cart API lives here entities/Product/ ui/ProductCard.tsx model/product.ts # pure domain type shared/ui/Button.tsx # only generic reusable stuff ``` Adding a new feature means creating one folder, not editing five. ### The key idea Classic architecture groups by file type: all buttons in `components/`, all API calls in `api/`, all utilities in `utils/`. At 2000 lines that works fine. At 30000 lines, adding a "cart" feature means touching 5+ directories and hunting for related files across the entire codebase. FSD flips this. The cart button lives with cart logic, cart API calls, and cart tests. One folder, one responsibility. A developer joining the team finds everything about orders in `entities/Order/` and never has to guess where the API call is hiding. ### Dependency rules Each layer sits at a fixed level in the hierarchy: ``` app (top - orchestrates everything) ↑ pages ↑ features ↑ entities ↑ shared (bottom - generic utilities only) ``` A `features/` slice can import from `entities/` or `shared/`. It cannot import from `pages/` or `app/`. The `shared/ui/Button.tsx` component knows nothing about your business domain. These rules exist because the moment `shared/` starts importing from `features/`, you get circular dependencies and the structure collapses. Slices inside the same layer also cannot import each other. `features/cart/` cannot import from `features/auth/`. If they need to share something, that something belongs in `entities/` or `shared/`. ### When to use - Team of 5+ developers on the same codebase: FSD enforces clear ownership without extra coordination. - Business-driven apps (e-commerce, SaaS dashboards): the `entities/` layer maps directly to your domain model. - Monorepo with shared libraries: `shared/` layer prevents duplication across packages. - Prototype under 1000 lines: skip it. The overhead costs more than the benefit. - Migrating a legacy project: start with `pages/` (natural route-based boundaries), then extract `entities/` from your existing `utils/`. ### Comparison: FSD vs. traditional structure | Aspect | Traditional (by-type) | FSD (by-feature) | |---|---|---| | Folder example | `components/CartButton/`, `api/cart.ts` | `pages/cart/ui/CartButton.tsx` | | Adding a feature | Edit 5+ folders | 1 folder | | Dependencies | Anything imports anything | Strict top-down only | | 100k+ LOC | Navigation becomes painful | Slices stay isolated | | Testing | Cross-folder mocks needed | Tests live in-slice | | Recommended for | Apps under 5k LOC | Production apps with teams | ### How FSD is enforced No framework does this for you. FSD is pure convention, and conventions break without tooling. The standard approach is `eslint-plugin-feature-sliced`, which lints layer violations. If a `features/auth/` file tries to import from `app/routing`, the linter flags it immediately. Vite and Webpack resolve imports normally. But because related code is co-located, tree-shaking works better. Local dependencies produce smaller chunks. Teams report roughly 20-30% smaller bundle sizes after migration, mostly from better dead-code elimination. One thing I've seen consistently in larger codebases: the biggest win is not bundle size. It's onboarding. A new developer who understands the layer names finds any file in under 30 seconds. ### Common mistakes **Dumping everything in `shared/`** ```tsx // Wrong: cart-specific component buried in shared // src/shared/ui/CartButton.tsx // Fix: move it where it belongs // src/features/cart/ui/CartButton.tsx ``` `shared/` is for truly generic code: design system components, utility functions, type helpers. When teams put domain-specific components there, `shared/` becomes a 500+ file dumping ground. Reddit threads on r/reactjs report 40% bundle bloat from this pattern alone. **Cross-importing within the same layer** ```tsx // Wrong: features importing features // src/features/cart/model/cart.ts import { useAuth } from 'src/features/auth'; // circular dependency risk // Fix: extract shared domain logic to entities // src/entities/User/model/authApi.ts ``` **Importing upward in the layer hierarchy** ```tsx // Wrong: feature reaching into the app layer import { router } from 'src/app/routing'; // ESLint violation // Fix: invert control with a callback export const loginSuccess = (onSuccess: () => void) => { // auth logic onSuccess(); }; // The pages layer calls: loginSuccess(() => router.navigate('/dashboard')) ``` **Missing public API barrel files** ```ts // Wrong: consumers must know internal paths import { Button } from 'src/shared/ui/Button/Button'; // Fix: barrel file controls what's public // src/shared/ui/index.ts export { Button } from './Button'; // Consumer: import { Button } from 'src/shared/ui' ``` Without index files, renaming anything inside a slice breaks consumers across the project. ### Real-world usage - **React + Next.js**: Saleor's `react-storefront` uses FSD patterns for `pages/product/` and `entities/Order/`. - **Vite + React**: the `fsd-vite` boilerplate has 10k+ GitHub stars. - **State management**: Zustand stores go in `features/cart/model/`, global user session state goes in `entities/User/model/`. - **Nx monorepo**: Angular and React projects use FSD layer structure mapped to Nx library boundaries. - **vs. Atomic Design**: Atomic Design is UI-focused (atoms, molecules, organisms). FSD is domain-focused. They solve different problems and can coexist: Atomic Design governs what lives inside `shared/ui/`. ### Follow-up questions **Q:** Walk me through a folder structure for a todo app. **A:** `app/` holds the router and providers. `pages/todo-list/` has the main page component. `features/add-todo/` has the form and submission logic. `entities/Todo/` has the Todo type and maybe a UI card. `shared/ui/` has Input and Button. **Q:** What happens when a component is used in two features? **A:** If the business logic differs, duplicate it. If it is pure domain (like a ProductCard), extract it to `entities/Product/ui/`. Duplication is cheaper than a premature abstraction. **Q:** How does FSD handle global state? **A:** Slice-local state (Zustand store, Redux slice) lives in `features/cart/model/`. User session and other truly global concerns go in `entities/User/model/` or `app/`. **Q:** Can FSD work alongside Atomic Design? **A:** Yes. Atomic Design can govern what goes inside `shared/ui/` (atoms, molecules, organisms). FSD governs the overall project structure. They operate at different levels and do not conflict. **Q:** How do you migrate a legacy project to FSD? **A:** Start with `pages/` because routes are already natural boundaries. Then identify domain objects and pull them into `entities/`. Finally move feature-specific code out of `components/` and `utils/`. The ESLint plugin acts as a safety net during migration. Teams report cutting import path complexity by 30% and onboarding time by half after a two-week migration. ## Examples ### Basic: add-to-cart feature slice Real e-commerce example with React and Zustand: ```tsx // src/features/cart/model/cartSlice.ts import { create } from 'zustand'; import { Product } from 'src/entities/Product/model/types'; // allowed: feature → entity interface CartState { items: Product[]; add: (item: Product) => void; } export const useCart = create<CartState>(set => ({ items: [], add: item => set(state => ({ items: [...state.items, item] })) })); // src/features/cart/ui/AddToCartButton.tsx import { useCart } from '../model/cartSlice'; // local import inside the slice export const AddToCartButton = ({ product }: { product: Product }) => { const add = useCart(state => state.add); return <button onClick={() => add(product)}>Add to cart</button>; }; ``` The button and its state live in the same slice. Tests go in `src/features/cart/`. Refactoring cart logic never touches anything outside this folder. ### Intermediate: page composing entities and features ```tsx // src/pages/user-orders/index.tsx import { UserOrderList } from './ui/OrderList'; // local to this page import { fetchUserOrders } from './model/ordersApi'; // local to this page import { Order } from 'src/entities/Order'; // allowed: page → entity export const UserOrdersPage = () => { const [orders, setOrders] = useState<Order[]>([]); useEffect(() => { fetchUserOrders().then(setOrders); }, []); return <UserOrderList orders={orders} />; }; ``` The page imports from `entities/` (lower layer, allowed) and from its own local files. No imports from other pages or features. ### Advanced: catching and fixing a layer violation This is the pattern that creates long-term coupling problems and fails the ESLint layer check: ```tsx // BAD: feature reaching up into the app layer // src/features/auth/model/auth.ts import { router } from 'src/app/routing'; // ESLint violation: imports from higher layer export const login = async (credentials: Credentials) => { await authApi.login(credentials); router.navigate('/dashboard'); // tightly coupled to app layer }; // GOOD: invert control with a callback // src/features/auth/lib/loginHandler.ts export const login = async ( credentials: Credentials, onSuccess: () => void ) => { await authApi.login(credentials); onSuccess(); }; // src/pages/login/index.tsx (page layer owns navigation) import { login } from 'src/features/auth'; const handleLogin = (credentials: Credentials) => { login(credentials, () => router.navigate('/dashboard')); }; ``` The fixed version keeps `features/auth/` independent of how navigation works. You can reuse `login()` on a different page, in a mobile app, or in tests without changing the feature itself.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.