Daniel Prescott

feature sliced: A Practical Frontend Architecture Guide

August 26, 2026

feature sliced: A Practical Frontend Architecture Guide

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.

LevelQuestion it answersExample
LayerHow much of the application does this code affect?features
SliceWhich business concept does it belong to?add-to-cart
SegmentWhat 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:

  • ui for components and presentation
  • model for state, schemas, selectors, and business rules
  • api for requests and data mapping
  • lib for internal supporting functions
  • config for 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:

  1. The page represents a route.
  2. Widgets form large sections of the page.
  3. Features provide user actions.
  4. Entities model recurring business concepts.
  5. 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.

AreaPotential benefitPossible drawback
NavigationBusiness-oriented folders make functionality easier to findNew developers must learn FSD terminology
RefactoringPublic APIs protect internal implementation detailsDesigning boundaries requires judgment
ScalingLayers provide predictable dependency directionSmall projects may gain unnecessary ceremony
TeamworkIndependent slices reduce overlapping changesInconsistent adoption creates confusion
TestingCohesive modules are easier to isolatePoorly designed slices can still be tightly coupled
ReuseDomain and generic reuse are clearly separatedTeams 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.

ApproachPrimary focusRelationship to FSD
Atomic DesignReusable interface compositionCan organize components inside a UI library
Domain-Driven DesignBusiness domains and domain modelsInfluences business-oriented boundaries
Clean ArchitectureDependency direction and separated concernsShares the principle of inward, controlled dependencies
MVCSeparation of model, view, and controllerOrganizes by technical responsibility rather than feature scope
Modular architectureIndependent, cohesive modulesFSD 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:

  1. Do developers struggle to locate all the code behind one user workflow?
  2. Do changes in one feature regularly break unrelated functionality?
  3. Does the project contain many cross-directory or circular imports?
  4. 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.

Article by Daniel Prescott

With over 10 years of experience in higher education advising and education policy, the Editor-in-Chief at GradeCalcs reviews GPA calculators, grading methodologies, and academic content to help ensure every resource is accurate, easy to understand, and useful for students, parents, and educators.

Leave a Comment