요즘 프론트엔드 구조화 이야기할 때 빠지지 않는 것은 FSD (Feature-Sliced Design)입니다. 실제로 프로젝트에 FSD를 적용해보면서 느낀 건,
"생각보다 경계가 애매하다", "사람마다, 팀마다 다르게 쓸 수도 있겠구나" 입니다.
이번 글에서는 제가 FSD를 직접 적용해보며 겪은 시행착오화 함께, 어떻게 구성했는지, 폴더 구조는 어떻게 짰는지, 그리고 어떤 고민이 있었는지를 솔직하게 정리해봤습니다.
FSD 기본 구조: Layers, Slices, Segments
FSD(Feature-Sliced Design)는 단순히 디렉토리를 나누는 것처럼 보일 수 있지만, 실제로는 명확한 개념 체계를 기반으로 한 설계 방식입니다. 이 구조를 이해하려면 세 가지 핵심 단어를 구분해야 합니다.
1. Layer - 전체 프로젝트를 나누는 수직적 레벨
2. Slice - 도메인 또는 기능 단위로 나누는 수평적 단위
3. Segment - 각 Slice 내부의 역할별 구성요소
1. Layer - 수직으로 쌓인 아키텍처의 골격
Layer는 FSD의 가장 상위 개념으로, 프로젝트를 관심사별로 나눈 큰 층입니다. 동시에 src/ 안에 있는 상위 폴더들이라고 보면 됩니다.
공식 문서 기준 폴더 구조는 보통 이렇게 생겼습니다.
src/
├── app/ # 앱 초기화, 글로벌 설정, 라우팅
├── pages/ # 라우트 단위 페이지
├── widgets/ # 여러 feature를 조합한 UI 구성 요소
├── features/ # 독립적인 기능 단위 (로그인, 댓글 작성 등)
├── entities/ # 비즈니스 도메인 객체 (User, Post 등)
├── shared/ # 공통 컴포넌트, 유틸, 타입, 디자인 시스템 등
예를 들어 feature/auth/login-form, entities/user, shared/ui/button 같은 식으로 구성됩니다.
이 Layer는 서로 의존 가능한 방향이 존재합니다. 보통 아래에서 위로만 의존 가능합니다.
shared → entities → features → widgets → processes → pages → app
✅ 예시
Layer | 설명 | 예시 |
shared/ | 모든 곳에서 사용할 수 있는 공통 UI, 함수, 타입 | Button.tsx, useDebounce, constant.ts |
entities/ | 핵심 비즈니스 도메인 단위 | user/, post/, product/ |
features/ | 특정 기능을 수행하는 모듈 | auth/login, comment/create |
widgets/ | 여러 feature나 entity를 조합한 UI | ProfileCard, PostListWithFilter |
pages/ | 라우팅 단위의 페이지 컴포넌트 | /profile, /login |
app/ | 앱 초기화, 라우터 설정 등 | App.tsx, Router.tsx |
2. Slice - 도메인 또는 기능 단위의 수평 구조
각 Layer 안에는 도메인이나 기능 단위로 Slice를 나눌 수 있습니다.
예를 들어 features/ 아래에는 이런 식으로 Slice가 생깁니다.
features/
├── auth/
│ ├── login/
│ └── logout/
├── comment/
│ └── create/
entities/ 에서는 도메인 중심으로 나눕니다.
entities/
├── user/
├── post/
├── product/
즉, Slice는 FSD의 수평 단위로 이해하면 됩니다.
하나의 Slice는 그 도메인 또는 기능에 필요한 모든 구성 요소를 가질 수 있습니다.
✅ 예시
- entities/user/ - 사용자 도메인
- features/auth/login/ - 로그인 기능
- features/comment/create/ - 댓글 작성 기능
- widgets/ProfileCard/ - 사용자 정보와 액션을 보여주는 UI
각 Slice는 독립적으로 테스트 가능하고, 다른 Slice와 명확히 분리되어야 합니다.
3. Segment - Slice 내부의 구성요소들
각 Slice 내부는 다시 Segment라고 부르는 작은 폴더들로 구성됩니다.
Segment는 역할에 따라 코드를 분리하기 위한 단위입니다. 보통 아래와 같이 나뉩니다.
features/auth/login/
├── model/ # 상태, store, 훅, types
├── ui/ # 시각적 컴포넌트
├── lib/ # 유틸 함수 또는 비즈니스 로직
├── api/ # API 호출 함수
Segment는 팀의 스타일에 따라 이름이나 구성이 달라질 수 있습니다.
예: services/, hooks/, types/, config/ 등
✅ 예시
features/
└── auth/
└── login/
├── model/
│ ├── useLogin.ts # 상태 관리
│ └── loginSlice.ts # Zustand 등
├── api/
│ └── loginApi.ts # 서버 통신 함수
├── lib/
│ └── validateForm.ts # 폼 유효성 검증
├── ui/
│ └── LoginForm.tsx # 로그인 폼 컴포넌트
이렇게 나누면, features/auth/login Slice 하나만 보면 기능 구현 전부를 이해할 수 있습니다.
사용하며 느낀 점
1. 정답은 없다.
FSD는 정해진 규칙을 따르는 방식이 아니라, 가이드라인에 가깝습니다.
팀의 개발 경험, 코드 규모, 도메인 복잡도에 따라 적용 방식이 달라질 수밖에 없습니다.
예를들어,
- 어떤 팀은 features와 entities를 엄격하게 나누고
- 어떤 팀은 둘을 구분하지 않고 하나의 domain/ 폴더로 운영하기도 합니다.
저도 예제 문서대로 구조를 나눠서 했지만, "이건 feature인가? entity인가?"하는 고민에 자꾸 멈춰서게 되었습니다.
예를들어, 저는 OpenLayers를 가장 많이 사용하는데요. 이 OpenLayers에서는 이런 객체들을 다룰 일이 많습니다.
- 지도 위에 마커로 표시되는 Point
- 사용자가 그린 Polygon이나 LineString
- 좌표 이동, 줌 조절 등 다양한 인터렉션
- 실제 도메인에 대응되는 데이터
이걸 FSD로 나눌 때 고민이 됩니다. 주로 "그리는 행위"는 feature, "그려진 결과"는 entity에 저장하였는데 개인적으로 이런 경우에는 더 코드를 관리하기 복잡하다고 느껴졌습니다.
2. 기준을 명확히 문서화하는 것이 핵심
FSD는 철저하게 디렉터리 구조와 의존성 규칙을 바탕으로 하기 때문에, 팀원 간의 인식 차이가 생기면 구조가 바로 무너질 수 있습니다.
그래서 저는 다음과 같은 것들을 간단한 내부 문서로 만들었습니다.
- entities와 features의 차이점 예시
- 디렉터리 구조 예시
- 레이어별 의존 가능/불가능 정리
- Slice 내부 Segment 구성 예시
이 문서를 보고 팀원도 같은 기준으로 폴더를 만들 수 있다면, 그게 성공적인 FSD 도입의 첫걸음이라고 생각합니다.
3. 처음부터 완벽하게 하려고 하지 말자
FSD는 처음부터 모든 걸 정교하게 나누려고 하면, 진입장벽이 너무 높아집니다.
저는 초기에 (여전히 종종 그러고 있습니다.) 너무 많은 시간을 "어디에 넣을지" 고민하는 데 썼고, 오히려 개발이 느려졌습니다.
그래서 다음과 같은 방식이 더 현실적이었습니다.
- 처음엔 기능 단위로만 거칠게 나누고 (features/, entities/도 안 나누고 그냥 domains/)
- 점점 Slice 내부를 분리하고, Layer도 쪼개기 시작
- 의존 방향도 점차 엄격하게 맞춰감
처음엔 지저분해도 괜찮습니다. 중요한 건 구조를 조금씩 정돈하는 방향으로 움직이는 것입니다.
4. FSD 구조 예시를 많이 들여다보자
직접 구조를 짜보는 것도 중요하지만, 다른 사람들은 어떻게 나눴는지를 관찰하는 것도 엄청 도움이 되었습니다.
GitHub나 오픈소스 프로젝트에서 실제로 FSD를 쓰는 예제를 보면 다양한 방식을 확인할 수 있습니다.
이걸 통해 나만 이런 고민을 하는 게 아니구나 싶었고, 내 프로젝트에 맞는 현실적인 구조는 어떤 모습일까 상상할 수 있습니다.
내 구조가 이상한게 아니라, 이건 원래 그렇게 다양한 방식으로 쓰이는 거더라고요.
FSD를 규칙이 아닌 기준으로서 받아들이자.
정리하자면:
1. 정답은 없고, 팀에 맞는 방식이 중요하다.
2. 기준과 예시를 명확하게 문서화하자.
3. 완벽한 구조보다 점진적인 정리가 더 현실적이다
4. "이건 feature인가, entity인가?"라는 질문은 자연스럽다. (또는 경우에 따라 합쳐서 설계하자.)
참고
(번역) 기능 분할 설계 - 최고의 프런트엔드 아키텍처
Feature-Sliced Design을 직접 사용하면서 느낀 장점과 단점
'Dev' 카테고리의 다른 글
[OAuth] OAuth 2.0을 파헤쳐보자 🔍 + 소셜로그인 예제 (0) | 2025.03.05 |
---|---|
[OAuth] GCP (Google Cloud Platform)에서 Client ID 생성하기 🪪 (0) | 2025.03.05 |
[OAuth] OAuth란? 🤔 (0) | 2025.03.04 |
React+MapBox Geojson 올리기 (Height 속성 활용하기 ) (0) | 2025.03.04 |
React+Mapbox 초기 세팅 🌍 (0) | 2025.03.04 |