Frontend projects rarely become difficult because of one badly written component. The real trouble begins when business logic, API requests, shared utilities, and interface elements become tangled across hundreds of files.
feature sliced commonly refers to Feature-Sliced Design (FSD), an architectural methodology for organizing frontend applications. It separates code into hierarchical layers, business-focused slices, and purpose-based segments while enforcing controlled dependencies and public APIs. The result is a codebase that is easier to understand, extend, test, and refactor as requirements change.
Feature-Sliced Design is especially associated with React, but its principles are not tied to a single framework. Teams can apply it to Vue, Next.js, and other component-based frontend applications.
What Is feature sliced Architecture?
Feature-Sliced Design is a collection of architectural rules and conventions for structuring frontend code. Its central idea is to organize the application around business concepts and user interactions instead of placing every component, service, hook, and utility in a global technical folder.
A conventional project might begin with this structure:
src/├── components/├── hooks/├── services/├── store/├── styles/└── utils/
This looks tidy when the application is small. As it grows, however, developers must search several unrelated directories to understand one workflow. A checkout function might involve components, hooks, state, validation, and API calls stored in five different places.
Feature-Sliced Design reorganizes that code according to its scope and purpose:
src/├── app/├── pages/├── widgets/├── features/├── entities/└── shared/
A user-facing action such as adding an item to a cart can live inside a dedicated add-to-cart feature. Its UI, state, validation, and supporting logic remain close together.
This encourages two valuable software-design properties:
- High cohesion: closely related code stays together.
- Loose coupling: modules know as little as possible about one another.
Feature-Sliced Design does not dictate a state-management library, rendering strategy, or visual design system. It provides boundaries within which teams can choose tools such as React, Redux Toolkit, Zustand, TanStack Query, or Next.js.
How the Architecture Is Organized
FSD uses three main organizational levels: layers, slices, and segments. Each answers a different question about the code.
| Level | Question it answers | Example |
|---|---|---|
| Layer | How much of the application does this code affect? | features |
| Slice | Which business concept does it belong to? | add-to-cart |
| Segment | What technical purpose does it serve? | ui, model, or api |
Layers define responsibility
Layers are standardized top-level groups arranged by scope. Code near the top can depend on layers beneath it, but lower layers should not import from higher ones.
A typical dependency direction looks like this:
app → pages → widgets → features → entities → shared
Not every application needs every layer. A small project might use only app, pages, and shared, introducing other layers when real architectural needs appear.
Slices represent business domains
Most layers are divided into slices. A slice groups files connected to a recognizable business concept or user goal.
Examples include:
features/├── add-to-cart/├── authenticate-user/├── change-password/└── filter-products/entities/├── product/├── order/└── user/
Slice names should reflect the product’s language. add-to-cart tells a developer more than a generic name such as form-handler or action-module.
The app and shared layers are exceptions. They usually describe application-wide infrastructure rather than business slices.
Segments group code by technical purpose
A slice may contain segments such as:
uifor components and presentationmodelfor state, schemas, selectors, and business rulesapifor requests and data mappinglibfor internal supporting functionsconfigfor slice-specific configuration
For example:
features/└── add-to-cart/ ├── api/ │ └── add-product.ts ├── model/ │ └── use-add-to-cart.ts ├── ui/ │ └── add-to-cart-button.tsx └── index.ts
Segments should be created because the slice needs them, not because a template says every directory must exist.
The Six Main Feature-Sliced Layers
Understanding what belongs in each layer prevents the architecture from turning into a renamed collection of miscellaneous folders.
App
The app layer contains application-wide initialization and composition. Common examples include:
- Root providers
- Global styles
- Routing configuration
- Error boundaries
- Dependency injection
- Analytics initialization
- Store configuration
This layer can use all layers below it because it assembles the final application.
app/├── providers/├── routes/├── styles/└── index.tsx
Avoid placing ordinary business logic here. Code belongs in app only when it coordinates or configures the application as a whole.
Pages
The pages layer contains route-level screens. A page composes widgets, features, and entities into a complete destination such as a product page, account settings screen, or order history.
pages/├── product-details/├── account-settings/└── order-history/
Pages should primarily perform composition. If a page accumulates reusable business behavior, move that behavior into an appropriate feature or entity.
Widgets
Widgets are substantial, reusable interface blocks that combine lower-level modules. A website header, product-results panel, account sidebar, or checkout summary can qualify as a widget.
A widget is more than a basic button or input. It represents a meaningful section of a page and may combine multiple features and entities.
widgets/├── site-header/├── product-catalog/└── checkout-summary/
A component used by only one page does not automatically need its own widget slice. It can remain inside that page until reuse or complexity justifies extraction.
Features
The features layer contains reusable actions that deliver value to a user. Good feature names usually describe an intention:
- Add a product to a cart
- Submit a review
- Filter search results
- Change an avatar
- Sign out
- Apply a discount code
Not every interaction is a feature. A purely visual accordion or generic modal belongs in shared UI. Creating a feature for every button produces excessive fragmentation.
Entities
Entities represent business concepts that the application uses repeatedly. Examples include user, product, article, invoice, and order.
An entity may contain:
- A domain data type
- A compact UI representation
- Formatting logic
- Entity-specific API functions
- State and selectors
- Data normalization rules
A product entity might expose a product card, price formatter, product type, and query utilities. A user action involving that product—such as adding it to a comparison list—would normally belong in features.
The entities layer is optional. If a simple application has no stable, reused domain concepts, forcing every data object into an entity adds ceremony without improving clarity.
Shared
The shared layer contains reusable code that does not know about specific business domains. Typical segments include:
shared/├── api/├── config/├── lib/├── routes/└── ui/
Buttons, date utilities, base API clients, environment configuration, and generic form controls commonly belong here.
The word “shared” does not mean “put anything reusable here.” A utility that understands products, orders, or authentication contains domain knowledge and should normally live closer to that domain.
Quick takeaway: Layers describe scope, slices describe business meaning, and segments describe technical purpose. Keeping these distinctions clear solves most placement decisions.
feature sliced Dependency Rules and Public APIs
Folders alone do not create an architecture. The import rules between them provide the real protection.
Imports should flow downward
A module may import from a lower layer but not from a higher one. For example:
- A page may import a widget, feature, entity, or shared module.
- A feature may import an entity or shared module.
- An entity may import from
shared. - A shared module must not import business code from entities or features.
This rule prevents foundational code from becoming dependent on the screens or workflows built on top of it.
Consider a currency formatter in shared/lib. If it imports an Order model from the entities layer, shared is no longer independent. A better solution is to accept a primitive number or generic value as input.
Slices should remain isolated
Slices within the same layer should generally avoid direct imports from one another. If features/add-to-cart depends directly on the internals of features/apply-coupon, changes can create an unpredictable chain of breakages.
Shared behavior can often be moved to:
- An entity representing the relevant domain concept
- A lower-level shared utility
- A widget or page that composes both features
- An explicitly permitted cross-reference where the methodology’s advanced rules justify it
Composition is usually safer than hidden peer-to-peer coupling.
Every slice needs a public API
A slice should expose its approved interface through an entry point, commonly index.ts.
// features/add-to-cart/index.tsexport { AddToCartButton } from"./ui/add-to-cart-button";export { useAddToCart } from"./model/use-add-to-cart";Consumers then import from the slice:
import { AddToCartButton } from"@/features/add-to-cart";They should not reach into its internal structure:
import { AddToCartButton } from"@/features/add-to-cart/ui/add-to-cart-button";A public API allows internal files to be moved or renamed without breaking every consumer. It also reveals which parts of a module are stable and intended for reuse.
Barrel files can create circular dependencies when used carelessly inside their own slice. Internal files should generally import one another directly, while outside consumers use the public entry point.
A Practical Project Structure Example
Imagine an online store with product browsing, authentication, filtering, and cart functionality.
src/├── app/│ ├── providers/│ ├── routes/│ └── styles/├── pages/│ ├── catalog/│ ├── product-details/│ └── checkout/├── widgets/│ ├── site-header/│ ├── product-grid/│ └── cart-summary/├── features/│ ├── add-to-cart/│ ├── filter-products/│ ├── authenticate-user/│ └── apply-coupon/├── entities/│ ├── product/│ ├── cart/│ └── user/└── shared/ ├── api/ ├── config/ ├── lib/ └── ui/
Here is how one product page might be composed:
import { ProductDetails } from"@/widgets/product-details";import { RelatedProducts } from"@/widgets/related-products";exportfunctionProductPage() {return (<><ProductDetails/><RelatedProducts/></> );}The ProductDetails widget could use the product entity to display data and the add-to-cart feature to provide an action. Neither lower-level module needs to know which page displays it.
This arrangement creates a visible chain of responsibility:
- The page represents a route.
- Widgets form large sections of the page.
- Features provide user actions.
- Entities model recurring business concepts.
- Shared modules supply domain-neutral foundations.
How to Implement Feature-Sliced Design
A successful migration starts with boundaries, not with moving every file into a new directory overnight.
1. Map the application’s business language
List the concepts and actions that appear repeatedly in requirements, interface labels, and team discussions.
For an educational platform, the entities might be student, course, and lesson. Features might include enroll-in-course, submit-assignment, and mark-lesson-complete.
This vocabulary gives slices meaningful names.
2. Establish aliases and layer directories
Configure a stable source alias such as @/ in TypeScript, the bundler, and the test runner. Then create only the layers the project genuinely needs.
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } }}Path aliases make import direction easier to recognize and reduce brittle relative paths.
3. Move generic foundations into shared
Start with uncontroversial modules:
- Base buttons and inputs
- API client configuration
- Generic formatting functions
- Environment variables
- Reusable validation helpers
Check each module for hidden domain knowledge before declaring it shared.
4. Identify stable entities
Extract domain concepts that appear across multiple pages or workflows. Keep each entity’s public API narrow and avoid creating an entity merely because an API returns a named object.
5. Extract complete user actions
Move one coherent workflow at a time into features. Include its UI, state, validation, and request logic when those pieces belong exclusively to that action.
This vertical migration is safer than relocating all components first, all hooks second, and all services last.
6. Compose widgets and pages
Once lower-level boundaries are clear, use widgets to assemble substantial interface regions and pages to compose route-level screens.
7. Enforce the architecture automatically
Code review alone is unreliable for a growing team. ESLint import restrictions, dependency analysis, and architecture linters such as Steiger can detect invalid imports and structural violations.
Automated checks are especially useful for preventing:
- Imports from higher layers
- Cross-slice dependencies
- Deep imports that bypass public APIs
- Circular dependency chains
Quick takeaway: Migrate by business workflow, keep changes small, and add automated boundary checks early. A perfect folder tree without enforcement will gradually lose its structure.
Benefits and Limitations
Feature-Sliced Design can produce a highly maintainable frontend, but it is not automatically the right choice for every codebase.
| Area | Potential benefit | Possible drawback |
|---|---|---|
| Navigation | Business-oriented folders make functionality easier to find | New developers must learn FSD terminology |
| Refactoring | Public APIs protect internal implementation details | Designing boundaries requires judgment |
| Scaling | Layers provide predictable dependency direction | Small projects may gain unnecessary ceremony |
| Teamwork | Independent slices reduce overlapping changes | Inconsistent adoption creates confusion |
| Testing | Cohesive modules are easier to isolate | Poorly designed slices can still be tightly coupled |
| Reuse | Domain and generic reuse are clearly separated | Teams may overuse shared as a dumping ground |
FSD works particularly well when:
- The frontend has substantial business logic.
- Requirements change frequently.
- Several developers contribute to the same codebase.
- Features must be developed or refactored independently.
- Technical folders have become difficult to navigate.
- Uncontrolled imports regularly cause regressions.
It may be excessive for a small landing page, short-lived prototype, or application with only a handful of routes and interactions.
Common Mistakes That Weaken the Architecture
Turning every component into a feature
A feature represents a user action or meaningful capability, not every clickable element. Generic inputs, icons, dropdowns, and layout components normally belong in shared UI.
Treating shared as a junk drawer
If developers cannot decide where code belongs, they often place it in shared. This eventually creates a second monolith.
Before moving something there, ask whether it mentions a business concept. If it understands users, products, subscriptions, lessons, or orders, it probably belongs in a domain-oriented slice.
Creating premature abstractions
Two similar components do not always need a shared abstraction. Their requirements may diverge later. Local duplication can be safer than coupling unrelated workflows through an inflexible generic module.
Bypassing public APIs
Deep imports make consumers dependent on internal directory layouts. Refactoring then becomes expensive, even when the module’s outward behavior has not changed.
Adding every possible layer
The methodology is adaptable. An application without reusable business entities does not need an empty entities layer. A simple page component does not need to be divided into a page, widget, feature, entity, and shared component merely to match a diagram.
Confusing UI hierarchy with business architecture
Atomic Design organizes interface elements into atoms, molecules, organisms, templates, and pages. Feature-Sliced Design organizes the wider application by scope and business responsibility.
The two approaches can coexist. Atomic principles may guide a design system inside shared/ui, while FSD governs dependencies and business modules across the application.
Feature-Sliced Design vs Other Architectures
Feature-Sliced Design overlaps with several established approaches but solves a more specific frontend organization problem.
| Approach | Primary focus | Relationship to FSD |
|---|---|---|
| Atomic Design | Reusable interface composition | Can organize components inside a UI library |
| Domain-Driven Design | Business domains and domain models | Influences business-oriented boundaries |
| Clean Architecture | Dependency direction and separated concerns | Shares the principle of inward, controlled dependencies |
| MVC | Separation of model, view, and controller | Organizes by technical responsibility rather than feature scope |
| Modular architecture | Independent, cohesive modules | FSD provides frontend-specific conventions for modularity |
FSD is not a replacement for every architectural idea. A team can use domain modeling, unidirectional state management, component composition, and a design system within the same Feature-Sliced project.
Is feature sliced Right for Your Project?
Choose the methodology when the cost of unclear boundaries has become greater than the cost of architectural conventions.
A practical evaluation can begin with four questions:
- Do developers struggle to locate all the code behind one user workflow?
- Do changes in one feature regularly break unrelated functionality?
- Does the project contain many cross-directory or circular imports?
- Are multiple developers or teams modifying the application simultaneously?
Several “yes” answers suggest that Feature-Sliced Design could provide useful structure.
Start with the dependency rule, public APIs, and the clearest business slices. Do not reorganize every file merely to achieve visual consistency. The goal is to make change safer, not to produce the most elaborate directory tree.
Feature-Sliced Design is most effective when the team treats boundaries as living architectural decisions. Document ambiguous placement choices, enforce imports automatically, and revisit slices as the product evolves.
Used pragmatically, feature sliced architecture gives frontend applications a shared vocabulary, predictable dependency flow, and modular structure without dictating the underlying framework or technology stack.
