Frontend projects rarely become difficult because developers cannot write components. They become difficult because nobody knows where new code belongs, which modules may depend on it, or what will break when it changes.
The term feature sliced commonly refers to Feature-Sliced Design (FSD), an architectural methodology for organizing frontend applications into layers, business-focused slices, and purpose-based segments. Its controlled dependency direction and public APIs help growing codebases remain understandable, modular, and easier to change.
Feature-Sliced Design is most closely associated with React and TypeScript projects, but its underlying rules are framework-independent. The same methodology can be adapted to Vue, Svelte, Next.js, Nuxt, and other component-based frontend environments.
What Does feature sliced Mean?
Feature-Sliced Design is a set of architectural conventions for structuring frontend source code around business responsibilities.
Instead of placing every component in one components directory, every request in services, and every hook in hooks, FSD groups related code according to three dimensions:
- Layers describe a module’s responsibility and dependency level.
- Slices group code by a business domain or user purpose.
- Segments group files inside a slice by technical purpose.
- Public APIs control what each slice exposes to the rest of the application.
The methodology’s primary purpose is to keep a frontend understandable as its features, developers, and business requirements change. It does not prescribe a state-management library, UI framework, API client, or testing tool.
That distinction matters. FSD is an application-structure methodology, not a complete technology stack.
Why Frontend Projects Need Structural Boundaries
A small application can work perfectly well with folders such as:
src/
├── components/
├── hooks/
├── services/
├── store/
├── types/
└── utils/
The weakness appears as the product grows. Files concerning checkout, authentication, products, and user profiles become distributed across every technical folder.
A developer changing the shopping cart may need to search through:
components/CartButton.tsxhooks/useCart.tsservices/cartApi.tsstore/cartSlice.tstypes/cart.tsutils/cartTotal.ts
Each file is categorized correctly by type, yet the business capability is fragmented. Dependencies also tend to become informal: components reach into stores, hooks call unrelated services, and utility directories accumulate domain logic.
Feature-Sliced Design reverses the emphasis. Business concepts become visible in the directory tree, while technical distinctions move inside those concepts.
src/
├── app/
├── pages/
├── widgets/
├── features/
├── entities/
└── shared/
This arrangement creates a common answer to three recurring questions:
- What responsibility does this code have?
- Which business area does it belong to?
- What other code is it allowed to use?
The Core feature sliced Architecture
The current FSD structure commonly uses six layers, arranged from highest to lowest:
| Layer | Main responsibility | Typical contents |
|---|---|---|
app | Application-wide setup | Routing, providers, global styles, initialization |
pages | Complete route-level screens | Product page, checkout page, profile page |
widgets | Large reusable interface blocks | Header, product grid, cart sidebar |
features | Valuable user interactions | Add to cart, sign in, apply coupon |
entities | Business concepts | User, product, order, article |
shared | Domain-neutral foundations | UI kit, API client, utilities, configuration |
Older examples may also show a processes layer. It was used for multi-page workflows, but it is deprecated in the current methodology; that logic is usually placed in more appropriate layers instead.
The App Layer
The app layer contains code that makes the application run as a whole:
- Router configuration
- Dependency providers
- Global state initialization
- Internationalization setup
- Global styles
- Error boundaries
- Analytics initialization
- Framework-specific entry points
Unlike most layers, app is not normally divided into business slices. Its segments might include providers, routes, and styles.
The Pages Layer
A page represents a screen or route that assembles lower-level modules.
For an online store, possible slices include:
pages/
├── catalog/
├── product-details/
├── cart/
└── checkout/
A page should mainly compose widgets, features, and entities. When substantial business logic accumulates directly inside a page component, that logic may belong in a lower layer.
The Widgets Layer
Widgets are substantial, self-contained interface blocks that combine multiple lower-level elements.
Examples include:
- Navigation header
- Product recommendations
- Shopping-cart drawer
- User account sidebar
- Comment feed
Not every visually large component is automatically a widget. A module belongs here when it forms a meaningful, independently identifiable block of a page.
The Features Layer
A feature represents a user interaction that delivers recognizable business value.
Good feature names usually describe actions:
add-to-cartsign-inchange-passwordfilter-productsapply-couponpublish-comment
A generic button is not a feature. An AddToCartButton connected to cart behavior may be part of one because it performs a meaningful action.
The features layer is optional. If an interaction is used only in one place and extracting it produces needless indirection, keeping it closer to its consumer can be clearer.
The Entities Layer
Entities model important business concepts such as:
- User
- Product
- Order
- Cart
- Article
- Comment
An entity slice can contain its data types, display components, state logic, API functions, and domain-specific helpers.
Avoid turning every noun into an entity. Because this is a low layer that much of the application can access, a poorly designed entity can become a widely coupled dependency. The official documentation specifically warns about excessive entities.
The Shared Layer
The shared layer contains reusable, domain-neutral infrastructure. Common segments include:
shared/
├── api/
├── config/
├── lib/
├── routes/
└── ui/
A date formatter, HTTP client, button, or environment configuration can belong here. A calculateCartDiscount helper usually should not, because it contains business knowledge about the cart.
A useful test is simple: could this module be moved to a different product without carrying the current product’s domain language? If not, it probably does not belong in shared.
Layers, Slices, and Segments Explained
The vocabulary becomes easier to understand when applied to one structure:
src/
└── features/ # layer
└── add-to-cart/ # slice
├── api/ # segment
├── model/ # segment
├── ui/ # segment
└── index.ts # public API
Here:
featuresidentifies the architectural responsibility.add-to-cartidentifies the business interaction.api,model, anduiidentify technical purposes.index.tsdefines the slice’s external contract.
What Is a Slice?
A slice is a cohesive group of modules associated with a business domain or meaningful purpose. Its name should reflect the language used by the product and development teams.
Slices are found mainly inside pages, widgets, features, and entities. The app and shared layers work differently because they do not normally represent independent business slices.
What Is a Segment?
A segment groups code by purpose inside a slice. Conventional names include:
uifor visual components and presentation logicmodelfor state, business rules, selectors, and schemasapifor backend interactionslibfor internal supporting functionsconfigfor slice-specific configuration
These names are conventions rather than mandatory folders. The official overview states that segment names are not strictly constrained. Create only the segments the slice actually needs instead of generating an empty directory tree for every module. See the official overview for the current terminology.
The FSD Import Rule
Feature-Sliced Design imposes a directional dependency rule:
A module in one slice may import from slices located only on strictly lower layers.
That produces this dependency flow:
| Code in this layer | May normally import from |
|---|---|
app | Pages, widgets, features, entities, shared |
pages | Widgets, features, entities, shared |
widgets | Features, entities, shared |
features | Entities, shared |
entities | Shared |
shared | Other permitted shared internals only |
For example, a module in features/add-to-cart can use entities/product and shared/api. It should not import widgets/cart-drawer or pages/checkout, because those modules sit above it.
This rule prevents lower-level modules from becoming coupled to the screens or compositions that consume them. The authoritative definition is available in the FSD layer reference.
Why Same-Layer Imports Cause Trouble
Slices on the same layer should generally remain independent. If features/add-to-cart imports directly from features/apply-coupon, neither feature has a clean boundary anymore.
Possible responses include:
- Compose both features in a widget.
- Move genuinely shared domain logic into an entity.
- Move domain-neutral logic into
shared. - Reconsider whether the two slices are really one cohesive feature.
Do not move code downward merely to silence an architectural warning. A lower layer makes a module more widely accessible, so its abstraction must genuinely belong there.
Public APIs and Encapsulation
Each slice exposes an intentional public API, commonly through an index.ts file:
// features/add-to-cart/index.ts
export { AddToCartButton } from "./ui/AddToCartButton";
export { useAddToCart } from "./model/useAddToCart";
Other slices import from the boundary:
import { AddToCartButton } from "@/features/add-to-cart";
They should avoid reaching into internal paths:
import { AddToCartButton }
from "@/features/add-to-cart/ui/AddToCartButton";
The first form lets the feature reorganize its internal files without forcing every consumer to change. The second exposes implementation details and weakens encapsulation.
A public API should remain small. Re-exporting every internal function through a barrel file technically creates an entry point, but it does not create a meaningful boundary.
The official slices and segments reference requires outside modules to use the declared public API rather than a slice’s internal file structure.
A Practical React and TypeScript Example
Consider a product page with product information and an add-to-cart action:
src/
├── app/
│ ├── providers/
│ └── routes/
├── pages/
│ └── product-details/
│ ├── ui/
│ │ └── ProductDetailsPage.tsx
│ └── index.ts
├── widgets/
│ └── product-overview/
│ ├── ui/
│ │ └── ProductOverview.tsx
│ └── index.ts
├── features/
│ └── add-to-cart/
│ ├── api/
│ │ └── addProduct.ts
│ ├── model/
│ │ └── useAddToCart.ts
│ ├── ui/
│ │ └── AddToCartButton.tsx
│ └── index.ts
├── entities/
│ └── product/
│ ├── model/
│ │ └── types.ts
│ ├── ui/
│ │ └── ProductCard.tsx
│ └── index.ts
└── shared/
├── api/
└── ui/
The responsibilities remain distinct:
ProductDetailsPagerepresents the route.ProductOverviewcomposes the visible page section.AddToCartButtonperforms a user action.ProductCarddisplays a business entity.shared/apiprovides domain-neutral network infrastructure.
This is more precise than placing all four components in a global components folder.
How to Implement feature sliced Step by Step
A successful adoption usually happens incrementally. Rewriting an entire application at once creates risk without proving that the proposed boundaries are correct.
1. Map Business Concepts and User Actions
List the application’s meaningful nouns and actions.
For an e-commerce application, nouns might include product, user, order, and cart. Actions might include add-to-cart, apply-coupon, and submit-order.
Use language already understood by product managers and developers. Vague names such as common, core, misc, and manager usually hide unclear responsibilities.
2. Establish App and Shared Foundations
Move truly global initialization into app. Place generic UI primitives, configuration, API infrastructure, and reusable technical helpers in shared.
Be conservative with shared. It should not become the old dumping ground with a new name.
3. Identify Pages and Large Compositions
Represent route-level screens in pages. Extract major reusable page blocks into widgets when they have a clear purpose.
Do not create a widget for every section automatically. A simple page-specific wrapper can stay inside its page slice.
4. Extract Stable Entities
Move established domain concepts into entities. Keep their public APIs focused and avoid exposing mutable internal state unnecessarily.
Entities are easiest to identify when the product already uses the concept consistently. Prematurely modeling uncertain concepts can produce abstractions that fight later requirements.
5. Extract Reusable User Features
Create a feature slice when an action has recognizable business value or requires isolated behavior, state, API calls, or reuse.
Start with obvious interactions. Do not split every click handler into a separate feature.
6. Add Public APIs
Define the supported exports of each slice and update external imports to use them. This creates a refactoring boundary before the internal organization becomes more complicated.
7. Enforce Dependency Direction
Review imports manually or use an architecture-aware linter. The FSD ecosystem provides Steiger, which checks project structure and architectural rules; it is referenced in the official FSD FAQ.
8. Migrate One Vertical Area at a Time
Move one page or business flow, test it, and document what the team learns. Temporary legacy boundaries are usually safer than a prolonged, all-or-nothing rewrite.
A product flow such as product details → add to cart → cart drawer offers a useful migration slice because it touches several layers while remaining understandable.
Using Feature-Sliced Design with Next.js
FSD can work with both the Pages Router and App Router, but framework routing and FSD’s app and pages layers should not be confused.
In a Next.js project, framework-required route files may remain in the root app or pages directory. Those thin route modules can import the corresponding page composition from the FSD structure under src.
app/
└── products/
└── [id]/
└── page.tsx
src/
└── pages/
└── product-details/
└── index.ts
With the App Router, pay particular attention to React Server Components and Client Components. If a slice’s normal index.ts exports server-only modules, importing that public API into a Client Component may pull server-only code into the client graph.
The official Next.js integration guide recommends a separate index.server.ts public API when server-only and client-compatible exports must be separated.
Benefits and Trade-Offs
| Area | Potential benefit | Practical trade-off |
|---|---|---|
| Navigation | Business concepts are visible in the tree | Developers must learn the vocabulary |
| Refactoring | Public APIs protect internal structure | Barrel exports require discipline |
| Dependencies | Layer rules reduce hidden coupling | Some compositions need restructuring |
| Team ownership | Slices can align with product areas | Cross-cutting concerns still need coordination |
| Reuse | Entities and shared modules have defined roles | Premature reuse creates weak abstractions |
| Scaling | Structure remains predictable as features grow | Full FSD may be excessive for tiny apps |
FSD does not eliminate architecture decisions. It gives teams a shared framework for making them.
Cross-cutting concerns such as authentication, permissions, analytics, and error handling can still span several business areas. The answer is not always to force them into a single feature slice. Some belong in application providers, shared infrastructure, entity models, or compositions at higher layers.
Feature-Sliced Design vs Other Approaches
| Approach | Primary organizing idea | What it handles well | What it does not fully define |
|---|---|---|---|
| Folder by type | Technical file category | Small-project simplicity | Business boundaries |
| Folder by feature | Product functionality | Colocation | Consistent dependency direction |
| Atomic Design | UI hierarchy | Design-system composition | Application business logic |
| Clean Architecture | Dependency inversion | Separation of concerns | Concrete frontend folders |
| Micro frontends | Independent applications | Deployment and team autonomy | Internal structure of each application |
| Feature-Sliced Design | Layers, domains, and purpose | Frontend module boundaries | Deployment strategy or technology selection |
FSD can complement several of these approaches. Atomic Design concepts can be used inside a UI library, while FSD governs where business-aware components live. A micro frontend can use FSD internally. Clean Architecture and Domain-Driven Design can also influence domain boundaries without being treated as identical methodologies.
Common Mistakes to Avoid
Creating Every Layer in a Small Project
All six layers are available, not mandatory. A modest application may need only app, pages, and shared at first.
Treating Every Component as a Feature
A feature represents user value or behavior, not merely a reusable component. Buttons, inputs, and modals usually belong in shared/ui when they are domain-neutral.
Putting Business Logic in Shared
shared is widely accessible. Domain-specific logic placed there becomes difficult to own and easy to misuse.
Creating Empty Segments
A slice containing empty api, model, lib, config, and ui folders gains ceremony, not clarity. Add a segment when real code needs that distinction.
Importing Through Internal Paths
Deep imports couple consumers to implementation details. Export a deliberate contract from the slice’s public API.
Splitting Slices Too Aggressively
Hundreds of tiny slices can make navigation worse. Prefer high cohesion: code that changes together for the same business reason often belongs together.
Migrating Without Team Agreement
A directory structure cannot enforce a shared mental model on its own. Document naming rules, dependency direction, public API expectations, and exceptions.
When Should You Use feature sliced Architecture?
Feature-Sliced Design is most useful when:
- The frontend will be maintained for years.
- Business requirements change frequently.
- Multiple developers work in the same codebase.
- The application has several domains and user flows.
- Uncontrolled imports make refactoring risky.
- New team members struggle to locate relevant code.
- Technical folders have become crowded with unrelated modules.
It may be unnecessary when:
- The project is a small landing page.
- The application has only a handful of components.
- The code will be short-lived.
- Business logic is minimal.
- A simple folder-by-feature structure already provides sufficient clarity.
A sensible rule is to adopt only the structure justified by current complexity while keeping a clear path for growth.
Final Takeaway
The feature sliced approach organizes frontend applications through responsibility-based layers, business-focused slices, purpose-based segments, directional imports, and explicit public APIs. Its value comes from making architectural boundaries visible and enforceable—not from creating a large folder tree.
Start with real business language, keep abstractions modest, expose narrow public APIs, and migrate one product area at a time. When those practices solve an actual coordination or maintenance problem, Feature-Sliced Design can give a growing frontend a structure that remains understandable as the product changes.