Skip to content
Innovialabs

Feature-Sliced Design: Scalable Frontend Architecture Explained

Advanced4 min

In short

Feature-Sliced Design splits a frontend into layers ordered by responsibility, each layer into business-domain slices, and each slice into technical segments; modules may import only from layers below them and only through a slice's public API.

What Feature-Sliced Design is

Feature-Sliced Design (FSD) is a methodology for structuring frontend code by business meaning rather than by file type. Instead of one huge components/ and hooks/ folder, code is grouped into layers, slices and segments, with one strict rule: dependencies only point downward. The result is a codebase where you can find, change or delete a feature without breaking unrelated parts.

Layers

Layers are top-level folders, from most specific to most generic:

LayerPurposeExample
appInitialization: providers, routing, global stylesTheme provider, store setup
pagesFull pages composed from lower layersProduct page, profile page
widgetsLarge self-contained UI blocksHeader, product list with filters
featuresUser actions that bring business valueAdd to cart, log in, leave a review
entitiesBusiness entitiesUser, product, order
sharedReusable code without business logicUI kit, API client, utils

Older docs also mention a processes layer; it is deprecated, and its logic usually moves to features or app. You do not need every layer: small projects often use only app, pages, shared and a few entities.

Slices and segments

Slices divide a layer by business domain: entities/user, entities/product, features/add-to-cart. The app and shared layers have no slices.

Segments divide a slice by technical purpose. Common names:

  • ui — components
  • model — state, stores, business logic
  • api — requests to the backend
  • lib — helpers specific to this slice
  • config — constants and flags

Import rules and public API

Two rules make FSD work:

  1. A module may import only from layers strictly below it. A feature can use entities and shared, but not widgets or another feature. Slices on the same layer do not import each other.
  2. Every slice exposes a public API through an index.ts. Other code imports only from that file, never from internal paths.
// entities/product/index.ts
export { ProductCard } from "./ui/product-card";
export { useProduct } from "./model/use-product";
export type { Product } from "./model/types";
// good
import { ProductCard } from "@/entities/product";
// bad: reaches into internals
import { ProductCard } from "@/entities/product/ui/product-card";

The linter Steiger and the ESLint plugin for boundaries can enforce these rules automatically, so architecture does not depend on code review alone.

When two entities genuinely need each other (an order references a user), FSD offers a cross-import notation with @x for explicit, controlled exceptions. Use it rarely.

A Next.js folder example

The Next.js app/ directory conflicts by name with the FSD app layer. A common solution: keep Next.js routing in the root app/ as thin files that re-export FSD pages, and move FSD code into src/.

app/                      # Next.js routing only
  products/[id]/page.tsx  # export { ProductPage as default } from "@/pages/product"
src/
  app/                    # providers, global styles
  pages/
    product/
      ui/product-page.tsx
      index.ts
  widgets/
    product-gallery/
  features/
    add-to-cart/
      ui/add-to-cart-button.tsx
      model/use-add-to-cart.ts
      index.ts
  entities/
    product/
      api/get-product.ts
      ui/product-card.tsx
      model/types.ts
      index.ts
  shared/
    ui/button.tsx
    api/http.ts

Be careful with the name pages too: Next.js can treat a pages folder (including src/pages) as Pages Router routes. The official FSD guide for Next.js describes how to avoid this clash; check it before fixing the structure.

Migrating an existing codebase

Do not rewrite everything at once. A gradual path:

  1. Create shared and move the UI kit, API client and utils there.
  2. Extract the main entities your app talks about (user, product, order).
  3. Move pages next, keeping them thin: they assemble, they do not contain logic.
  4. Extract features as you touch code for new tasks.
  5. Turn on linting for import rules once the structure stabilizes, and fix violations gradually.

The most common difficulty is deciding what is a feature and what is an entity. A practical test: an entity is a noun (product), a feature is a verb the user performs (add product to cart).

When FSD is overkill

  • Landing pages, small marketing sites and prototypes.
  • Short-lived MVPs where speed matters more than structure.
  • A solo developer on a small app with a few screens.
  • Teams that will not enforce the rules; half-applied FSD is worse than a simple, consistent structure.

FSD pays off on projects with several developers, many features and a long life.

FAQ

No. It is framework-agnostic and is used with Vue, Svelte, Angular and others. The layers and import rules stay the same.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.