Feature Sliced Design: Frontend Architecture Guide

Frontend projects rarely become difficult because of one poorly written component. Problems emerge gradually: business logic spreads across generic folders, dependencies point everywhere, reusable components become tightly coupled, and developers no longer know where new code belongs.

Feature sliced design is a frontend architectural methodology that organizes application code into standardized layers, business-focused slices, and purpose-based segments. Its downward dependency rule, isolated modules, and explicit public APIs help teams manage growing applications without scattering related logic across components, hooks, services, and utility folders.

Developers searching for feature sliced architecture usually mean Feature-Sliced Design, commonly abbreviated as FSD. It is particularly associated with React and TypeScript projects, although its organizational principles can also work with Vue, Angular, and other component-based frontend technologies.

What Is Feature Sliced Design?

Feature-Sliced Design is a set of architectural rules and conventions for structuring frontend applications. Instead of arranging most files solely by technical type, it combines three organizational levels:

  1. Layers define the responsibility and permitted dependency direction.
  2. Slices group code by business domain or product meaning.
  3. Segments organize files inside a slice by technical purpose.

The architecture aims to create high cohesion and low coupling. Code serving the same business purpose stays close together, while unrelated areas of the application remain isolated.

Consider a conventional frontend structure:

src/├── components/├── hooks/├── services/├── stores/├── types/└── utils/

This may work well initially. As the product expands, however, implementing one feature can require changes across nearly every folder. A shopping-cart update might involve a component in components, a hook in hooks, an endpoint in services, state in stores, and interfaces in types.

A Feature-Sliced Design structure communicates business meaning more clearly:

src/├── app/├── pages/├── widgets/├── features/├── entities/└── shared/

Each layer has a defined role, and modules generally import only from layers below them. The result is not automatic simplicity, but a predictable framework for deciding where code belongs.

How the Feature Sliced Architecture Works

The central hierarchy is:

Layer → Slice → Segment

For example:

features/└── add-to-cart/    ├── ui/    ├── model/    ├── api/    ├── lib/    └── index.ts

Here:

  • features is the layer.
  • add-to-cart is the business-oriented slice.
  • ui, model, api, and lib are segments.
  • index.ts defines the slice’s public API.

These levels answer different questions:

LevelQuestion answeredExample
LayerWhat responsibility does this code have?features
SliceWhich business concept or user action does it serve?add-to-cart
SegmentWhat technical purpose does it perform?ui, api, or model

This distinction prevents two common problems: an enormous global collection of technical folders and a completely unregulated feature-folder system.

The Standard Feature Sliced Layers

The official model defines seven historical layers, ordered from highest to lowest:

  1. app
  2. processes — deprecated
  3. pages
  4. widgets
  5. features
  6. entities
  7. shared

Modern projects normally omit processes. They also do not need to use every remaining layer. The official layer reference recommends adding a layer only when it provides genuine architectural value.

App

The app layer contains application-wide setup and composition. Typical responsibilities include:

  • Routing configuration
  • Global providers
  • Store initialization
  • Error boundaries
  • Global styles
  • Application entry points
  • Analytics initialization
  • Dependency injection

Unlike most layers, app does not contain business slices. It is divided directly into segments because it represents the application as a whole.

app/├── providers/├── routes/├── styles/└── index.tsx

Business components and reusable domain logic should not accumulate here merely because they are used by the application.

Pages

The pages layer contains complete route-level screens. A page composes widgets, features, entities, and shared resources into something the router can display.

pages/├── catalog/├── product-details/├── checkout/└── profile/

Each folder represents a page slice. A catalog page should not directly import the checkout page because slices on the same layer are intended to remain independent.

Current FSD guidance favors a pages-first decomposition. Start with meaningful pages and move code into lower layers only when reuse or clearer ownership justifies it. This approach helps prevent hundreds of tiny abstractions.

Widgets

Widgets are substantial, independently meaningful interface blocks that combine lower-level modules. Examples include:

  • Site header
  • Product grid
  • Shopping-cart sidebar
  • Article feed
  • Profile summary
  • Checkout form

A widget is larger than a basic UI control but smaller than an entire page. It may combine several entities and features into a cohesive interface section.

Not every component deserves to become a widget. A button belongs in shared/ui, while a page-specific section can remain inside its page slice until another use case appears.

Features

The features layer represents reusable user interactions that provide business value. Feature names often read like verbs:

  • sign-in
  • add-to-cart
  • filter-products
  • change-password
  • submit-review
  • toggle-favorite

A feature can contain its UI, state, validation, API calls, and supporting utilities. It should describe an action rather than a complete page or a passive business object.

A useful test is to finish this sentence:

A user can ______.

If the answer is “add a product to the cart,” that behavior may be a feature. If the answer is merely “product,” it is more likely an entity.

Avoid turning every interaction into a global feature immediately. Logic used by only one page can remain local to that page.

Entities

Entities represent recognizable business concepts, usually expressed as nouns:

  • user
  • product
  • order
  • article
  • comment
  • payment

An entity slice can include a domain model, focused UI representation, API integration, and business-specific helpers.

entities/└── product/    ├── ui/    │   └── ProductCard.tsx    ├── model/    │   └── product.ts    ├── api/    │   └── getProduct.ts    └── index.ts

Entities should not become automatic wrappers around every backend resource. A type returned by an API does not necessarily deserve its own entity slice. Create an entity when the concept has meaningful frontend behavior, representation, or reuse.

Shared

The shared layer contains reusable, business-agnostic foundations. Common segments include:

shared/├── api/├── config/├── lib/└── ui/

Suitable content includes:

  • API client configuration
  • Design-system controls
  • Generic formatting functions
  • Environment configuration
  • Framework adapters
  • Reusable technical hooks
  • Basic utility libraries

The shared layer should not become a dumping ground. A function belongs there because it is genuinely independent of a particular business domain—not simply because several files use it.

Like app, shared has segments but no business slices.

Slices and Segments in Feature Sliced Projects

Layers provide the broad architecture, but slices provide most of the domain isolation.

What Is a Slice?

A slice is a cohesive collection of files related to a business concept, user action, or page. Slice names depend on the application rather than a universal vocabulary.

For an online store, possible slices include:

entities/productentities/orderfeatures/add-to-cartfeatures/apply-couponwidgets/product-listpages/checkout

For a publishing platform, the vocabulary changes:

entities/articleentities/commentfeatures/create-commentwidgets/article-feedpages/article-editor

Good slices reflect the language used by product managers, designers, developers, and users. That alignment makes the folder structure easier to navigate during actual product work.

What Is a Segment?

Segments separate files by their purpose within a slice. Common segment names include:

SegmentTypical contents
uiComponents, styles, display formatters
modelState, schemas, business rules, selectors
apiapiRequests, response types, data mappers
libInternal helpers used by the slice
configConfiguration and feature flags

Segment names should communicate purpose. Generic folders such as components, hooks, and types only describe the form of the code. They reveal little about why it exists.

A React hook that manages checkout validation belongs in the checkout slice’s model or lib segment according to its responsibility. It does not need a separate hooks segment merely because it uses React’s hook syntax.

The Feature Sliced Import Rule

The dependency rule is the architectural backbone of FSD:

A module inside a slice may import other slices only from layers strictly below its own layer.

The normal dependency direction is therefore:

app → pages → widgets → features → entities → shared

Code does not need to pass through every intermediate layer. A page can import directly from shared, and a widget can import from entities. The rule only prevents upward dependencies and, in most cases, imports between separate slices on the same layer.

ImportNormally allowed?Reason
pages/checkoutfeatures/apply-couponYesFeatures are below Pages
features/add-to-cartentities/productYesEntities are below Features
entities/productshared/apiYesShared is below Entities
entities/productfeatures/add-to-cartNoUpward dependency
features/cartfeatures/paymentNoCross-import on the same layer
Within features/cartYesFiles belong to the same slice

This structure reduces circular dependencies and prevents foundational modules from becoming aware of the higher-level workflows that consume them.

Why Same-Layer Cross-Imports Are Risky

Suppose features/cart directly imports internal state from features/payment. The two slices are now coupled even though their layer suggests that they are independent.

That coupling can cause:

  • Unclear ownership
  • Hidden integration requirements
  • Fragile tests
  • Bidirectional dependencies
  • Refactoring failures
  • Business flows scattered across slices

A better solution may be to compose both features inside a page or widget. If two slices constantly depend on each other, their boundary may be artificial and they may need to be merged.

Cross-imports are not physically impossible; they are an architectural warning. The official cross-import guidance recommends treating them as deliberate exceptions and reviewing the underlying boundaries.

Public APIs and Encapsulation

Every externally consumed slice should expose a public API, normally through index.ts.

// features/add-to-cart/index.tsexport { AddToCartButton } from"./ui/AddToCartButton";export { useAddToCart } from"./model/useAddToCart";

Consumers import from the slice boundary:

import {AddToCartButton,useAddToCart} from"@/features/add-to-cart";

They should avoid deep imports:

import { cartStore } from"@/features/add-to-cart/model/internal/cartStore";

Deep imports expose internal implementation details. Once several consumers depend on those paths, reorganizing the slice becomes risky.

A well-designed public API:

  • Shows how the slice is intended to be used
  • Hides private files and internal state
  • Reduces accidental coupling
  • Makes refactoring safer
  • Improves code discovery
  • Creates a stable contract for tests and consumers

Do not export every internal symbol from index.ts. That produces a barrel file, but not meaningful encapsulation.

The @x Notation for Entity Relationships

Real business entities often reference one another. An order may contain products, or a song may reference an artist. Because entity-to-entity imports occur on the same layer, FSD offers an explicit @x mechanism for exceptional cross-references.

entities/├── product/│   ├── @x/│   │   └── order.ts│   └── index.ts└── order/

The order entity can then use a deliberately restricted product interface:

importtype { ProductForOrder } from"@/entities/product/@x/order";

The notation makes the coupling visible. It should remain a narrow escape hatch, not the default method of connecting every entity.

Before introducing @x, consider whether:

  • The entities were divided too finely
  • The relationship belongs in shared/api
  • Generic types could accept type parameters
  • A higher layer could compose the objects
  • The slices should be merged

A Practical Feature Sliced Project Structure

A medium-sized commerce application might use the following organization:

src/├── app/│   ├── providers/│   ├── routes/│   └── styles/├── pages/│   ├── catalog/│   ├── product-details/│   ├── cart/│   └── checkout/├── widgets/│   ├── header/│   ├── product-grid/│   └── cart-summary/├── features/│   ├── add-to-cart/│   ├── apply-coupon/│   ├── filter-products/│   └── sign-in/├── entities/│   ├── product/│   ├── order/│   └── user/└── shared/    ├── api/    ├── config/    ├── lib/    └── ui/

The checkout page might compose a cart-summary widget, coupon feature, order entity, and shared layout component:

import { CartSummary } from"@/widgets/cart-summary";import { ApplyCoupon } from"@/features/apply-coupon";import { OrderTotal } from"@/entities/order";import { PageLayout } from"@/shared/ui";exportfunctionCheckoutPage() {return (<PageLayout><CartSummary/><ApplyCoupon/><OrderTotal/></PageLayout>  );}

This page controls composition without forcing the lower-level modules to know about one another.

How to Adopt Feature Sliced Design

Rewriting an established application all at once creates unnecessary risk. Incremental migration is usually more effective.

1. Map the Existing Pages

List the application’s actual routes and major screens. Pages are familiar boundaries and provide a practical starting point.

Create page slices and move route-level composition into them. Keep page-specific logic local while its ownership remains clear.

2. Establish App and Shared Foundations

Move global initialization into app. Move truly reusable, business-independent infrastructure into shared.

Be conservative with shared. If a module still speaks the language of orders, subscriptions, products, or accounts, it probably belongs in a business-aware layer.

3. Keep Local Code Local

Do not extract an entity, feature, or widget merely to make the structure appear complete. Code used by one page can stay in that page.

Extract it when:

  • Multiple pages need it
  • It has a stable business identity
  • Its independent boundary improves testing
  • Its public interface is clear
  • Reuse is already real rather than hypothetical

4. Extract Reusable Business Concepts

Move passive domain concepts into entities, reusable user actions into features, and substantial interface compositions into widgets.

Use product vocabulary when naming slices. Clear names such as reset-password and order-history are more useful than vague labels such as management or common.

5. Define Public APIs

Add an intentional index.ts to each externally used slice. Replace deep imports gradually and keep private implementation files unexported.

6. Enforce Dependency Boundaries

Architecture that exists only in documentation will eventually drift. Teams can protect it through:

  • TypeScript path aliases
  • ESLint import restrictions
  • Dependency-analysis tools
  • Code-review checks
  • Architectural tests
  • Steiger, the official FSD project-structure linter

Steiger can identify issues such as insignificant slices and excessive slicing:

npx steiger src

Linting should support architectural decisions, not replace them. A technically valid dependency may still represent a weak domain boundary.

7. Migrate One Area at a Time

Select a frequently changed but manageable workflow. Move it, test it, document the decisions, and use the experience to refine team conventions before migrating the next area.

Quick takeaway: Begin with pages, app-wide setup, and a clean shared foundation. Extract lower-level slices only when the code demonstrates a genuine need for reuse or independent ownership.

Benefits and Trade-Offs

Feature-Sliced Design offers useful constraints, but it is not universally appropriate.

AreaPotential benefitPossible drawback
NavigationBusiness-oriented folders are easier to locateThe hierarchy takes time to learn
DependenciesDownward imports reduce cyclesSome natural domain relationships become awkward
RefactoringPublic APIs protect internalsBarrel files require maintenance
CollaborationStandard conventions improve onboardingTeams must agree on slice boundaries
ReuseLocal and reusable code are separated deliberatelyPremature extraction causes over-engineering
ScalingLarge applications gain predictable structureSmall applications may gain unnecessary ceremony

FSD works especially well when:

  • The frontend contains substantial business logic
  • Requirements change frequently
  • Several developers work in the same codebase
  • Features cross multiple technical concerns
  • Existing folders have unclear ownership
  • Refactoring regularly breaks unrelated functionality

It may be excessive when:

  • The application is a small static site
  • The product has only a few screens
  • Most logic lives outside the frontend
  • The project is a short-lived prototype
  • One developer can understand the entire codebase easily

A small project can adopt only app, pages, and shared. Using fewer layers is often more faithful to the methodology than inventing empty folders for every possible abstraction.

Feature Sliced Design Compared with Other Approaches

Feature Sliced Design vs Atomic Design

Atomic Design classifies interface components as atoms, molecules, organisms, templates, and pages. Its main concern is UI composition.

Feature-Sliced Design governs the wider application architecture, including business logic, API access, state, dependencies, and public module boundaries.

The approaches can coexist. Atomic Design concepts may be used inside a shared/ui design system, while FSD controls the broader project structure.

Feature Sliced Design vs Clean Architecture

Clean Architecture emphasizes dependency inversion and separation between enterprise rules, use cases, interface adapters, and frameworks.

FSD provides frontend-specific folder conventions and business-oriented module boundaries. It is typically easier to apply directly to a React or Vue repository, while Clean Architecture operates at a more abstract system-design level.

Feature Sliced Design vs Domain-Driven Design

Domain-Driven Design focuses on modeling complex business domains through bounded contexts, entities, aggregates, value objects, and a shared language.

FSD borrows the value of domain-oriented naming but does not implement the full DDD model. Its entities and slices are frontend organizational constructs, not automatic equivalents of DDD entities or bounded contexts.

Feature Sliced Design vs Simple Feature Folders

A feature-folder architecture groups related files together but often leaves dependency direction and module boundaries undefined.

FSD adds standardized layers, import rules, segments, and public APIs. Those constraints are the main distinction—not simply the presence of folders named after features.

Common Feature Sliced Mistakes

Creating Every Layer on Day One

Empty layers add no value. Use only the layers the application needs.

Splitting Too Early

Hundreds of tiny slices make navigation harder and increase boilerplate. Keep page-specific logic within its page until reuse becomes evident.

Treating Shared as a Junk Drawer

A shared folder full of business rules recreates the coupling FSD is intended to prevent. Shared code should remain broadly reusable and domain-independent.

Confusing Entities with Features

Entities are usually business nouns, such as user or product. Features are generally user actions, such as sign-in or add-to-cart.

Allowing Deep Imports

Deep imports bypass public APIs and connect consumers to internal folder structures. Export a deliberate interface from the slice root.

Hiding Cross-Imports Behind Aliases

A path alias can make an invalid dependency look tidy, but it does not repair the architecture. Examine why two same-layer slices need one another.

Forcing Framework Concepts into the Hierarchy

React components, hooks, stores, and TypeScript types are implementation forms—not business boundaries. Organize them according to their responsibility within the appropriate slice.

Is Feature Sliced the Right Choice?

Choose feature sliced architecture when your frontend’s business complexity has outgrown informal folder conventions and the team needs predictable ownership, dependency direction, and module boundaries.

Start smaller if the project is simple. A structure based on app, pages, and shared may be sufficient. Introduce widgets, features, and entities only when actual reuse and domain complexity justify them.

The strongest implementation is rarely the one with the most folders. It is the one where developers can answer three questions quickly:

  1. Where should this code live?
  2. Which modules may it depend on?
  3. What part of it may other modules use?

Feature-Sliced Design provides consistent answers through layers, slices, segments, downward dependencies, and explicit public APIs. Applied gradually—and without excessive slicing—it can turn a difficult frontend codebase into a structure that remains understandable as both the product and development team grow.

A
Written & Reviewed By

Dr. Alexandra Reed

Reviews and publishes educational physics content focused on accuracy, conceptual clarity, and student learning. Specializes in physics fundamentals, formulas, equations, problem-solving methods, and academic study resources designed to support high school, college, and competitive exam learners.

Latest Physics Articles