Project Structure
Use this project structure to separate application composition, business workflows, domain concepts, and external data access. The recommendations are adapted from Feature-Sliced Design.
Disclaimer
These recommendations were developed for internal Qualcomm use and are now available for general reference. They reflect our experience and may not be applicable to every situation.
NOTE
This guide was updated in 2026 to reflect changing industry standards. Barrel files are no longer recommended, and segment rules have been loosened for better DX.
The FSD methodology is based on three levels of abstraction:
Overview
1. Layers
A layer is a top-level directory and the first level of application decomposition. This structure uses seven layers:
- app: This is where the application logic is initialized. Providers, routers, global styles, etc. are defined here. It serves as the entry point of the application.
- pages: This layer includes the application's pages.
- widgets: These are standalone UI components used on pages.
- features: This layer groups code for cohesive business capabilities and workflow-specific responsibilities. A feature may coordinate entities, data operations, UI state, and business rules when the application needs that boundary.
- entities: This layer represents domain concepts such as users, reviews, and comments.
- data: This layer organizes access to external data by business domain.
- shared: This layer contains reusable components, transport infrastructure, configuration, and utilities that are not tied to specific business logic.
These layers help organize the codebase and promote modular, maintainable, and scalable architecture.
Risk of Change
- Higher layers: Changes here impact the entire system. They require thorough testing and validation.
- Lower layers: Changes can ripple upward. Modifying a lower layer may affect multiple higher layers. Therefore, altering lower layers involves more risk.
In summary, higher layers bear more responsibility and knowledge, while lower layers are more abstract but riskier to modify. It's essential to strike a balance and design layers that promote maintainability and minimize risk.
2. Slices
Slices represent a way to organize code within a layer. They group together files that implement specific functionality related to the business logic.
Purpose and Scope
- Functionality: Slices focus on a particular aspect of the application's functionality. For example, you might have slices for user authentication, data processing, or reporting.
- Project Context: The impact of slices depends on the specific project. Some projects may have many slices, while others might have only a few.
Implementation
- Folder Structure: Slices are typically organized as folders or directories. Each slice contains related files, such as controllers, models, and views.
- Module Isolation: By grouping related files together, slices promote modularity and maintainability. Changes within a slice should have no impact on other slices in the same layer.
3. Segments
Segments are divisions within a slice. They aren't always required, particularly if your slice only contains a few files. Each segment is responsible for a distinct part of the slice's functionality:
assets/– media assets like images and videos.model/– local state, business rules, and presentation types.ui/– UI components responsible for displaying data.
Data slices can also contain an api/ segment for API methods, queries, mutations, external interfaces, and validation schemas.
NOTE
If a slice is small enough, you can omit segments entirely. Use segments when the slice is large enough to warrant separation.
Characteristics
- Independent Units: Each segment is responsible for a distinct part of the module's technical implementation.
- Focused Scope: Segments handle specific tasks, such as data processing (
model), data display (ui), or, within Data, communication with external services (api).
Layer Guidelines
App
The App layer contains the overall initialization logic of the application – various wrappers, global data stores, and styles.
Pages
Pages represent the individual routes or screens within the application. Each page should maintain a minimalist structure, delegating the business logic to the underlying layers. As such, a page groups related operations through a composition of widgets, features, and entities.
Widgets
Widgets are independent and full-featured blocks of pages with specific actions. It consists of self-sufficient UI blocks that emerge from the composition of lower-level units like entities and features. It may also be used as an organizational layer to satisfy layer constraints.
However, it's important to note that the Widgets layer is a compositional layer and does not typically contain business logic on its own. Business logic is typically organized in features and entities.
Features
Treat the Features layer as a responsibility boundary for business capabilities and workflows. A feature slice groups code that belongs together in a specific business context. Depending on the context, it may combine entities, data operations, UI state, and business rules, but it does not need to contain all of them.
Use these distinctions as guidelines:
- An entity represents a reusable domain concept, such as a Product or User, together with state, rules, types, or presentation.
- A feature represents behavior or state that belongs to a business capability or workflow, such as CommentComposer, ProductComparison, or CheckoutEligibility.
- A widget composes entities and features into a substantial page block without becoming the default owner of their business rules.
Features often support user-facing actions, but they do not need to be directly user-triggered. For example, a CommentComposer might own draft state, validation, and submission, while a ProductComparison feature might group selected products and comparison rules without performing a mutation.
Choose the boundary that keeps a responsibility cohesive. Keep concept-specific behavior in Entities, workflow-specific behavior in Features, and page-block composition in Widgets.
Entities
Entities are the components related to the representation of business entities, i.e. the "bricks" that are used to build the business logic. These are typically the terms that the business uses to describe the product.
Each slice in this layer contains some or all of the following:
- simple UI elements
- state and business rules, like jotai atoms and their selectors
- domain-specific calculations and state transformations
Note that models and state for a particular domain may also be in the Data layer.
Consider the following example:
Segments
model/: Entity state, computed values, and the input and output types used by related UI and features.ui/: UI components within entities accept props for content and callback props for interactions.
When designing UI components in the entities layer, keep their interfaces shallow and focused on the domain data they present.
Pass Data Through Props
A feature, widget, or page can use the data layer and map the result to an entity component's properties. The component remains focused on rendering the domain concept regardless of where the data originated.
Benefits of Shallow UI Components
- Flexibility: You can easily swap out the data source (e.g., switch from an API to a local database) without modifying the UI component.
- Testability: Shallow components are simpler to test because they focus solely on rendering and don't contain complex logic.
- Reusability: A well-defined
Productcomponent can be reused across different features or screens.
Example: E-commerce Application
In an e-commerce application, the entities could be Product and Order. The Product entity might render a product name, derive availability labels, and provide a product summary or card.
If product selection is shared across workflows, the Product entity can own that state. Both ProductSearch and OrderCheckout import the atom from the entity layer, so the feature slices remain isolated from each other.
entities/product/model/selected-product.ts:
import {atom} from "jotai"
export interface Product {
id: string
name: string
}
export const selectedProductAtom = atom<Product | null>(null)features/product-search/product-search.tsx:
import {useSetAtom} from "jotai"
import {
selectedProductAtom,
type Product,
} from "~entities/product/model/selected-product"
interface ProductSearchProps {
results: Product[]
}
export function ProductSearch({results}: ProductSearchProps) {
const selectProduct = useSetAtom(selectedProductAtom)
return (
<ul>
{results.map((product) => (
<li key={product.id}>
<button onClick={() => selectProduct(product)} type="button">
Select {product.name}
</button>
</li>
))}
</ul>
)
}features/order-checkout/order-checkout.tsx:
import {useAtomValue} from "jotai"
import {selectedProductAtom} from "~entities/product/model/selected-product"
export function OrderCheckout() {
const selectedProduct = useAtomValue(selectedProductAtom)
if (!selectedProduct) {
return <p>Select a product before checking out.</p>
}
return <button type="button">Checkout {selectedProduct.name}</button>
}Data
The default FSD structure does not include a data layer, but we have added one for clarity. While FSD recommends placing API logic in the shared layer, this approach complicates the separation by business entity. The constructs in the shared layer are designed to be agnostic and not tied to any specific business entity.
All external data operations and their contracts live in the Data layer. This includes interfaces for external payloads, validation schemas, API methods, queries, and mutations. Organize data slices by business domain and expose their supported operations and validated results through each slice's public entrypoint.
Shared
Shared contains business-agnostic primitives. Name shared modules by their technical responsibility, such as table-filters, config, or auth, rather than by a business entity or workflow.
Generic transport infrastructure, such as an HTTP client, request serialization, and common error handling, belongs in Shared. Data-layer slices use that infrastructure to implement domain-specific API methods.
Rules
Layer Restrictions
FSD organizes code based on its responsibility and dependencies. It achieves this in part through strict, unidirectional data flow between layers. This data flow has several benefits:
- Each layer has a clear zone of responsibility, which makes the code more understandable and maintainable.
- FSD promotes low coupling, which means dependencies between slices are regulated. A module in a slice can only import other slices when they are located on layers strictly below.
| Layer | Can use | Can be used by |
|---|---|---|
| app | pages, widgets, features, entities, data, shared | |
| pages | widgets, features, entities, data, shared | app |
| widgets | features, entities, data, shared | app, pages |
| features | entities, data, shared | app, pages, widgets |
| entities | data, shared | app, pages, widgets, features |
| data | shared | app, pages, widgets, features, entities |
| shared | app, pages, widgets, features, entities, data |
The lower a slice is in the hierarchy, the riskier it is to refactor. For example, changing transport infrastructure in the shared layer can significantly impact the entire application.
Slice Isolation
Slices are simply subfolders of a layer. Each slice represents a specific thing, and has rules that govern how they're used:
- Slices of the same layer cannot use each other directly.
- The
dataandsharedlayers are exempt from this restriction.
- The