Organizing Large Frontends The LIFT vs. Feature-Sliced Design Dilemma
Lukas Schneider
DevOps Engineer · Leapcell

Introduction
As frontend applications grow in complexity and scale, the way we organize our codebase becomes critically important. Without a well-defined structure, projects can quickly devolve into an unmanageable mess, leading to slower development cycles, increased bugs, and a steep learning curve for new team members. Two prominent architectural patterns have emerged to address these challenges: LIFT (Locating Logic Intuitively Fast) and Feature-Sliced Design (FSD). Both offer distinct approaches to modularity and maintainability, but they cater to different philosophies and project needs. This article will deconstruct LIFT and FSD, comparing their principles, implementation strategies, and suitable use cases to help you make an informed decision for your next large-scale frontend project.
Core Concepts Explained
Before diving into the intricacies of LIFT and FSD, let's clarify some foundational terms that are central to this discussion.
- Modularity: The degree to which a system's components can be separated and recombined. High modularity means components are independent and interchangeable, easing development and maintenance.
- Separation of Concerns: The principle of dividing a computer program into distinct features that overlap in functionality as little as possible. Each component or module should ideally handle one specific concern.
- Encapsulation: The bundling of data with the methods that operate on that data and restricting direct access to some of the component's parts. This is often achieved through file or folder boundaries.
- Domain-driven Design (DDD): An approach to software development that centers on defining a domain model. In frontend, this translates to organizing code around business concepts rather than technical concerns.
- Scalability: The ability of a system to handle a growing amount of work or its potential to be enlarged to accommodate that growth. In code organization, this means the pattern can efficiently support a large number of files and features without becoming unwieldy.
LIFT: Locating Logic Intuitively Fast
Principles and Philosophy
LIFT, an acronym for Locating Logic Intuitively Fast, is a set of guidelines popularized by Angular. Its core philosophy revolves around making it easy for developers to find relevant code quickly, regardless of the project's size. LIFT suggests four primary rules:
- Locate: Files for a specific feature should be located in a single, intuitive place. This usually means grouping files related to a feature together in its own directory.
- Identify: The names of files should immediately indicate what they contain. For example,
user-list.component.ts
clearly identifies a file as an Angular component for a user list. - Flat: Keep folder structures as flat as possible until a feature grows significantly. This avoids deep nesting that can make navigation cumbersome.
- Try to be DRY (Don't Repeat Yourself): Avoid redundant code.
The primary goal of LIFT is developer experience and discoverability. When a developer needs to work on a specific feature, they should be able to quickly navigate to its dedicated folder and find all related files.
Implementation and Example
In a LIFT-based structure, you typically see features grouped into top-level directories. Inside each feature directory, you'd find all its components, services, models, and tests.
Consider a simple e-commerce application. A LIFT structure might look like this:
src/
├── app/
│ ├── core/ // Application-wide services, interceptors, etc.
│ │ ├── auth/
│ │ │ ├── auth.service.ts
│ │ │ └── ...
│ │ └── ...
│ ├── shared/ // Reusable UI components, utility functions
│ │ ├── components/
│ │ │ ├── button/
│ │ │ │ ├── button.component.ts
│ │ │ │ └── button.component.html
│ │ │ └── ...
│ │ └── pipes/
│ │ └── ...
│ ├── features/ // Main business features
│ │ ├── product/ // 'product' feature
│ │ │ ├── components/
│ │ │ │ ├── product-card/
│ │ │ │ │ ├── product-card.component.ts
│ │ │ │ │ └── product-card.component.html
│ │ │ │ └── product-list/
│ │ │ │ ├── product-list.component.ts
│ │ │ │ └── product-list.component.html
│ │ │ ├── services/
│ │ │ │ └── product.service.ts
│ │ │ ├── models/
│ │ │ │ └── product.model.ts
│ │ │ ├── product.module.ts
│ │ │ └── product.routes.ts
│ │ ├── cart/ // 'cart' feature
│ │ │ ├── components/
│ │ │ │ ├── cart-item/
│ │ │ │ └── cart-view/
│ │ │ ├── services/
│ │ │ └── ...
│ │ └── user/
│ │ └── ...
│ └── app.component.ts
│ └── app.module.ts
│ └── app.routes.ts
└── environments/
└── main.ts
In this example, all files related to the product
feature (components, services, models, routing) reside within the src/app/features/product
directory. This makes it intuitive to find all relevant code when working on product-related functionality.
Application Scenarios
LIFT is particularly well-suited for:
- Small to medium-sized applications: Where the number of features is manageable, and developers can easily hold the overall structure in their heads.
- Teams prioritizing quick feature navigation: When the speed of finding code is paramount for developer productivity.
- Projects with clear domain boundaries: Once features are defined, all their internal parts are tightly coupled.
However, as applications grow, the boundaries between features can start to blur, and managing dependencies across feature modules can become challenging. Shared components or services might end up in various feature folders, or a "shared" folder can become a dumping ground.
Feature-Sliced Design (FSD)
Principles and Philosophy
Feature-Sliced Design (FSD) takes a more opinionated and structured approach to code organization, aiming to create a highly scalable and maintainable architecture for large and complex applications. FSD introduces the concept of "slices" and "layers" with strict rules for inter-layer communication and dependency management. Its core principles are:
- Layers: The application is divided into horizontal layers, each with a specific responsibility (e.g.,
app
,pages
,widgets
,features
,entities
,shared
). - Slices: Within each layer, components are grouped into "slices" that represent a self-contained part of the application's domain (e.g.,
product-card
,user-profile
). - Vertical Cohesion, Horizontal Isolation: Code related to a specific feature or slice is grouped vertically across layers, while distinct slices within the same layer are isolated from each other.
- Strict Dependency Rules: Higher layers can depend on lower layers, but not the other way around. This unidirectional flow prevents circular dependencies and promotes robust architecture.
- Public API: Each slice should expose a well-defined public API to maintain encapsulation and control external interactions.
FSD emphasizes architectural clarity, encapsulation, and scalability as the primary goals. It aims to prevent codebase entropy by enforcing strict rules, making it easier to manage complexity as the application grows.
Implementation and Example
FSD typically organizes a project into a hierarchical structure based on layers, and then further into slices within those layers.
src/
├── app/ // Application-wide logic, routing, global styles
│ ├── providers/ // Global context providers, e.g., Redux store, auth context
│ │ ├── store.ts
│ │ └── auth.ts
│ ├── layouts/ // Core layouts (e.g., header, footer, sidebar)
│ │ ├── base-layout/
│ │ │ ├── index.ts
│ │ │ └── ui.tsx
│ │ └── ...
│ ├── index.ts
│ └── styles/
│ └── global.css
├── pages/ // Full-page components, composition of features/widgets
│ ├── home/
│ │ ├── index.ts
│ │ └── ui.tsx
│ ├── product-details/
│ │ ├── index.ts
│ │ └── ui.tsx
│ └── profile/
│ └── ...
├── widgets/ // Combinations of related features/entities, like a dashboard widget
│ ├── product-list-widget/
│ │ ├── index.ts
│ │ └── ui.tsx
│ └── user-profile-card/
│ └── ...
├── features/ // Concrete business logic with specific interactions (e.g., "add-to-cart")
│ ├── add-to-cart/
│ │ ├── index.ts // Public API for the feature like `addToCartModel`
│ │ ├── api/ // API calls
│ │ ├── model/ // Redux slice, state management
│ │ ├── ui.tsx // UI component, can depend on `model`
│ │ └── lib/ // Utils
│ ├── sign-in/
│ │ └── ...
│ └── filter-products/
│ └── ...
├── entities/ // Domain-specific entities like "product", "user", "order"
│ ├── product/
│ │ ├── index.ts // Public API for the entity like `productModel`
│ │ ├── api/ // API calls related to products
│ │ ├── model/ // Redux slice, state management for product data
│ │ ├── ui.tsx // UI component for displaying product data (e.g., bare product item)
│ │ └── lib/ // Utils
│ ├── user/
│ │ └── ...
│ └── ...
├── shared/ // Reusable and generic code (UI components, utils, config, lib)
│ ├── ui/ // Generic UI components (e.g., Button, Input)
│ │ ├── button/
│ │ │ ├── index.ts
│ │ │ └── ui.tsx
│ │ └── input/
│ │ └── ...
│ ├── lib/ // Utility functions (e.g., date-formatter)
│ │ └── formatters.ts
│ ├── config/ // Application-wide constants
│ │ └── apiUrl.ts
│ └── api/ // Generic API utility
│ └── baseApi.ts
└── index.tsx // Entry point
In this FSD example:
- The
product-details
page
integratesproduct
entity
(for data display) and anadd-to-cart
feature
(for interaction). - The
add-to-cart
feature depends on theproduct
entity (to know what to add) and potentially onshared/ui
for a button. - Dependencies always flow downwards:
pages
can usewidgets
,features
,entities
, andshared
.features
can useentities
andshared
.entities
can only useshared
. This strict rule prevents higher-level logic from leaking into lower layers and maintains clear boundaries.
Application Scenarios
FSD is highly effective for:
- Large and extremely complex applications: Where long-term maintainability and scalability are paramount.
- Large teams: With many developers working concurrently on different parts of the application, FSD's strict rules minimize conflicts and ensure architectural consistency.
- Projects requiring high architectural rigor: When preventing technical debt and enforcing clean architecture is a top priority.
- Applications with evolving requirements: The modularity allows for easier addition, removal, or modification of features without significant ripple effects.
The overhead of learning FSD's conventions and setting up its strict structure can be higher initially, making it less suitable for very small, rapidly prototyped applications where this level of rigor isn't justified.
Conclusion
Both LIFT and Feature-Sliced Design offer valuable approaches to organizing large frontend projects, each with its strengths and trade-offs. LIFT prioritizes developer intuition and fast code location, making it excellent for smaller to medium-sized applications or teams valuing quick feature navigation. In contrast, Feature-Sliced Design provides a highly structured and scalable framework with strict dependency rules, ideal for large, complex applications and big teams where long-term maintainability and architectural integrity are critical. The choice between LIFT and FSD ultimately depends on your project's size, team structure, and long-term architectural goals. By understanding their core principles, you can select the pattern that best empowers your team to build robust and maintainable frontend applications.