Skip to main content

Layers

Layers are the first level of organisational hierarchy in Feature-Sliced Design. Their purpose is to separate code based on how much responsibility it needs and how many other modules in the app it depends on. Every layer carries special semantic meaning to help you determine how much responsibility you should allocate to your code.

There are 7 layers in total, arranged from most responsibility and dependency to least:

A file system tree, with a single root folder called src and then seven subfolders: app, processes, pages, widgets, features, entities, shared. The processes folder is slightly faded out. A file system tree, with a single root folder called src and then seven subfolders: app, processes, pages, widgets, features, entities, shared. The processes folder is slightly faded out.
  1. App
  2. Processes (deprecated)
  3. Pages
  4. Widgets
  5. Features
  6. Entities
  7. Shared

You don't have to use every layer in your project β€” only add them if you think it brings value to your project. Typically, most frontend projects will have at least the Shared, Pages, and App layers.

In practice, layers are folders with lowercase names (for example, πŸ“ shared, πŸ“ pages, πŸ“ app). Adding new layers is not recommended because their semantics are standardized.

Import rule on layers​

Layers are made up of slices β€” highly cohesive groups of modules. Dependencies between slices are regulated by the import rule on layers:

A module (file) in a slice can only import other slices when they are located on layers strictly below.

For example, the folder πŸ“ ~/features/aaa is a slice with the name "aaa". A file inside of it, ~/features/aaa/api/request.ts, cannot import code from any file in πŸ“ ~/features/bbb, but can import code from πŸ“ ~/entities and πŸ“ ~/shared, as well as any sibling code from πŸ“ ~/features/aaa, for example, ~/features/aaa/lib/cache.ts.

Layers App and Shared are exceptions to this rule β€” they are both a layer and a slice at the same time. Slices partition code by business domain, and these two layers are exceptions because Shared does not have business domains, and App combines all business domains.

In practice, this means that layers App and Shared are made up of segments, and segments can import each other freely.

Layer definitions​

This section describes the semantic meaning of each layer to create an intuition for what kind of code belongs there.

Shared​

This layer forms a foundation for the rest of the app. It's a place to create connections with the external world, for example, backends, third-party libraries, the environment. It is also a place to define your own highly contained libraries.

This layer, like the App layer, does not contain slices. Slices are intended to divide the layer into business domains, but business domains do not exist in Shared. This means that all files in Shared can reference and import from each other.

Here are the segments that you can typically find in this layer:

  • πŸ“ api β€” the API client and potentially also functions to make requests to specific backend endpoints.
  • πŸ“ ui β€” the application's UI kit.
    Components on this layer should not contain business logic, but it's okay for them to be business-themed. For example, you can put the company logo and page layout here. Components with UI logic are also allowed (for example, autocomplete or a search bar).
  • πŸ“ lib β€” a collection of internal libraries.
    This folder should not be treated as helpers or utilities (read here why these folders often turn into a dump). Instead, every library in this folder should have one area of focus, for example, dates, colors, text manipulation, etc. That area of focus should be documented in a README file. The developers in your team should know what can and cannot be added to these libraries.
  • πŸ“ config β€” environment variables, global feature flags and other global configuration for your app.
  • πŸ“ routes β€” route constants or patterns for matching routes.
  • πŸ“ i18n β€” setup code for translations, global translation strings.

You are free to add more segments, but make sure that the name of these segments describes the purpose of the content, not its essence. For example, components, hooks, and types are bad segment names because they aren't that helpful when you're looking for code.

Entities​

Slices on this layer represent concepts from the real world that the project is working with. Commonly, they are the terms that the business uses to describe the product. For example, a social network might work with business entities like User, Post, and Group.

An entity slice might contain the data storage (πŸ“ model), data validation schemas (πŸ“ model), entity-related API request functions (πŸ“ api), as well as the visual representation of this entity in the interface (πŸ“ ui). The visual representation doesn't have to produce a complete UI block β€” it is primarily meant to reuse the same appearance across several pages in the app, and different business logic may be attached to it through props or slots.

Entity relationships​

Entities in FSD are slices, and by default, slices cannot know about each other. In real life, however, entities often interact with each other, and sometimes one entity owns or contains other entities. Because of that, the business logic of these interactions is preferably kept in higher layers, like Features or Pages.

When one entity's data object contains other data objects, usually it's a good idea to make the connection between the entities explicit and side-step the slice isolation by making a cross-reference API with the @x notation. The reason is that connected entities need to be refactored together, so it's best to make the connection impossible to miss.

For example:

entities/artist/model/artist.ts
import type { Song } from "entities/song/@x/artist";

export interface Artist {
name: string;
songs: Array<Song>;
}
entities/song/@x/artist.ts
export type { Song } from "../model/song.ts";

Learn more about the @x notation in the Public API for cross-imports section.

Features​

This layer is for the main interactions in your app, things that your users care to do. These interactions often involve business entities, because that's what the app is about.

A crucial principle for using the Features layer effectively is: not everything needs to be a feature. A good indicator that something needs to be a feature is the fact that it is reused on several pages.

For example, if the app has several editors, and all of them have comments, then comments are a reused feature. Remember that slices are a mechanism for finding code quickly, and if there are too many features, the important ones are drowned out.

Ideally, when you arrive in a new project, you would discover its functionality by looking through the pages and features. When deciding on what should be a feature, optimize for the experience of a newcomer to the project to quickly discover large important areas of code.

A feature slice might contain the UI to perform the interaction like a form (πŸ“ ui), the API calls needed to make the action (πŸ“ api), validation and internal state (πŸ“ model), feature flags (πŸ“ config).

Widgets​

The Widgets layer is intended for large self-sufficient blocks of UI. Widgets are most useful when they are reused across multiple pages, or when the page that they belong to has multiple large indepdendent blocks, and this is one of them.

If a block of UI makes up most of the interesting content on a page, and is never reused, it should not be a widget, and instead it should be placed directly inside that page.

tip

If you're using a nested routing system (like the router of Remix), it may be helpful to use the Widgets layer in the same way as a flat routing system would use the Pages layer β€” to create full router blocks, complete with related data fetching, loading states, and error boundaries.

In the same way, you can store page layouts on this layer.

Pages​

Pages are what makes up websites and applications (also known as screens or activities). One page usually corresponds to one slice, however, if there are several very similar pages, they can be grouped into one slice, for example, registration and login forms.

There's no limit to how much code you can place in a page slice as long as your team still finds it easy to navigate. If a UI block on a page is not reused, it's perfectly fine to keep it inside the page slice.

In a page slice you can typically find the page's UI as well as loading states and error boundaries (πŸ“ ui) and the data fetching and mutating requests (πŸ“ api). It's not common for a page to have a dedicated data model, and tiny bits of state can be kept in the components themselves.

Processes​

caution

This layer has been deprecated. The current version of the spec recommends avoiding it and moving its contents to features and app instead.

Processes are escape hatches for multi-page interactions.

This layer is deliberately left undefined. Most applications should not use this layer, and keep router-level and server-level logic on the App layer. Consider using this layer only when the App layer grows large enough to become unmaintainable and needs unloading.

App​

All kinds of app-wide matters, both in the technical sense (e.g., context providers) and in the business sense (e.g., analytics).

This layer usually doesn't contain slices, as well as Shared, instead having segments directly.

Here are the segments that you can typically find in this layer:

  • πŸ“ routes β€” the router configuration
  • πŸ“ store β€” global store configuration
  • πŸ“ styles β€” global styles
  • πŸ“ entrypoint β€” the entrypoint to the application code, framework-specific