
들어가며
보통 프론트엔드 프로젝트를 시작할 때면 아래와 비슷한 루트 폴더 구조를 구성했습니다.

components, hooks, utils처럼 파일의 역할별로 폴더를 나누는 방식인데요. 프로젝트가 작을 때는 어디에 뭘 둘지 고민할 필요가 없어 이걸로 충분합니다.
문제는 기능을 붙여갈수록 컴포넌트 파일 하나가 200줄, 300줄을 넘기면서 시작됩니다. 길어진 함수와 비즈니스 로직, 상수, 컴포넌트를 파일로 쪼개 한 파일의 가독성은 챙길 수 있지만, 이번엔 components, hooks, utils 폴더마다 파일이 수십 개로 불어나 어디서부터 봐야 할지 막막해집니다.
이런 문제를 마주했을 때 고민해볼 수 있는 Feature-Sliced Design(FSD)에 대해 소개해보겠습니다.
1. FSD란?
Feature-Sliced Design(FSD)은 프론트엔드 애플리케이션을 기능 단위로 나누고, 그 사이의 의존성 방향을 규칙으로 강제하는 아키텍처 방법론입니다.
FSD는 코드를 세 가지 축으로 나눕니다.
- 레이어: 기능적 역할에 따른 관심사 분리
- 슬라이스: 레이어 안을 비즈니스 도메인 단위로 분리
- 세그먼트: 슬라이스 안을 기술적 관심사 단위로 분리

1.1 레이어
앞서 본 역할 기반 폴더 구조와 비슷한 형태입니다.
- App - 라우팅, 글로벌 스타일, 프로바이더 등의 전역 설정
- Pages - 페이지 단위 (ex: /login, /products, /cart, …)
- Widgets - 재사용 가능한 큰 UI 단위 (ex: 헤더, 푸터, 사이드바, …)
- Features - 특정 비즈니스 기능 단위 (ex: 로그인, 회원가입, 장바구니, …)
- Entities - 프로젝트가 다루는 비즈니스 Entity(ex: User, Product, Order, …)
- Shared - 모든 Layer에서 재사용되는 코드(ex: UI 컴포넌트, 유틸 함수, 상수, 타입, …)
(* process 계층은 여러 페이지를 아우르는 비즈니스 로직을 담는 레이어로 사용되었지만, 역할이 모호하고 사용 빈도가 낮아 현재는 제거되었습니다.)
모든 레이어를 사용할 필요는 없고 프로젝트에서 필요한 레이어만 선택적으로 사용하면 됩니다. 단, 상위 레이어 → 하위 레이어 단방향 의존을 지켜야 합니다.

단방향 의존성이 규칙으로 강제되긴 하지만, 사실 상위 레이어가 하위 레이어를 참조하는 흐름은 그 자체로 자연스럽습니다.
예를 들어 로그인 페이지(pages)는 로그인 기능(features)을 가져다 화면을 구성하고, 그 로그인 기능은 사용자 정보를 다루기 위해 User 엔티티(entities)를 참조합니다. 이렇게 상위에서 하위로 흐르는 방향은 우리가 평소 화면을 조립하는 방식과 그대로 맞아떨어집니다.
1.2 슬라이스
각 레이어 안에서 비즈니스 도메인 단위로 폴더를 나누는 것을 슬라이스라고 합니다.
예를 들어 entities 레이어 안에는 user, product, order처럼 프로젝트가 다루는 도메인별로 슬라이스가 생깁니다.
src/
├─ entities/ # 비즈니스 도메인 레이어
│ ├─ user/ # 사용자 도메인
│ ├─ product/ # 상품 도메인
│ └─ order/ # 주문 도메인
│
└─ features/ # 특정 비즈니스 기능 레이어
├─ auth/ # 로그인 / 회원가입 기능
├─ cart/ # 장바구니 기능
└─ search/ # 검색 기능같은 레이어 안의 슬라이스들은 서로를 직접 참조할 수 없습니다.

슬라이스는 상품 도메인, 주문 도메인, 사용자 도메인처럼 서로 다른 비즈니스 도메인을 나타내므로, 서로를 직접 참조하는 것은 자연스럽지 않습니다. 따라서 슬라이스 간 의존은 상위 레이어를 통해서만 이루어져야 합니다.
다만 모든 레이어에 슬라이스가 있는 것은 아닙니다. app과 shared는 특정 도메인에 속하지 않는 전역 코드이므로, 슬라이스 없이 바로 하위 분류인 세그먼트로 나뉩니다.
1.3 세그먼트
세그먼트는 마지막 계층으로 슬라이스 안에서 기술적 관심사 단위로 폴더를 나누는 것을 의미합니다.
- ui: Component, Date Formatter, Style 등
- api : Request Function, Data Type, Mapper 등
- model: Schema, Interface, Store, Business Logic 등
- lib: Slice 내부에서 사용하는 Library 코드
- config: Configuration, Feature Flag etc. 설정 관련 코드
2. 컴포넌트를 어디에 둘지 판단하기
오늘의집 메인 화면에 상품 카드를 하나 추가한다고 가정하고, 이 컴포넌트를 어디에 둘지 판단하는 과정을 따라가보겠습니다.

화면에 보이는 상품 카드(상품 이미지, 이름, 가격, 할인율)를 컴포넌트로 만든다면, FSD의 세 축인 레이어 → 슬라이스 → 세그먼트 순서로 자리를 좁혀가면 됩니다.
- 레이어 — 어느 층에 속하는가?
- 어디서든 쓰는 순수 UI(
Button,Input)나 유틸이면 →shared - 비즈니스 데이터를 보여주는 표현 단위면 →
entities - 사용자의 행위(담기, 찜)를 처리하면 →
features - 여러 도메인·기능을 묶은 큰 블록이면 →
widgets
상품 카드는 상품 정보를 “보여주는” 표현 단위이므로 entities입니다.
- 슬라이스 — 어느 도메인인가?
entities 안에서 다시 도메인 단위로 나뉩니다. 이 카드는 상품을 다루므로 product 슬라이스에 들어갑니다.
- 세그먼트 — 어떤 기술적 성격인가?
슬라이스 안에서는 기술적 관심사로 나뉩니다. 카드는 화면에 그려지는 컴포넌트이므로 ui 세그먼트입니다.
3. 도입 후기
좋았던 점은 두 가지였습니다.
첫째, 폴더 구조만 봐도 코드의 역할이 어느 정도 드러납니다. features/cart는 장바구니 기능을, entities/product는 상품 도메인 코드를 담고 있어 경로 자체가 설명이 됩니다.
둘째, 변경의 영향 범위를 예측하기 쉽습니다. 레이어 간 단방향 의존성과 슬라이스 격리 덕분에, 한 곳을 수정해도 변경이 어디까지 전파될지 가늠할 수 있습니다. 역할 기반 구조에서 느꼈던 “이 수정이 어디까지 영향을 줄지 모르겠다”는 불안이 줄었습니다.
반대로 아쉬운 점도 있었습니다. 파일 하나를 만들 때마다 “이건 어디에 둬야 하지?”를 계속 판단해야 한다는 점입니다. 특히 api 세그먼트는 거의 모든 레이어에 둘 수 있어, 재사용 범위를 기준으로 어디에 둘지 규칙을 정해야 했습니다.
그래서 처음부터 모든 레이어를 갖추기보다, 최소한의 구조로 시작해 재사용이 생기거나 책임이 분명해지는 시점에 하위 레이어로 분리하는 방식이 잘 맞았습니다. 카드 컴포넌트도 메인에서만 쓰면 pages/main/ui에 두고, 여러 페이지에서 상품을 표현하게 되면 entities/product/ui로 옮기는 식이었습니다.
결국 FSD의 의의는 정해진 폴더 구조를 그대로 따르기보다 프로젝트가 자라는 과정에서 흩어지는 코드들이 일정한 기준 아래 일관된 구조로 수렴하게 만드는 것이 아닐까 싶습니다.