FSD 도입기
올해 3월 초에 실 서비스를 목적으로 프로젝트를 하나 기획했다. 서비스를 장기 운영할 목적으로 기획했기 때문에 유지보수와 확장이 용이하도록 프로젝트를 설계해야겠다고 판단했다.
계속 진행해 왔던 폴더 구조는 아래와 같이 화면 중심으로 설계를 했었다. 하지만 이 구조는 화면이 바뀔 경우 디펜던시가 많아 수정 작업이 많이 발생했고, 개발 도중 병합 충돌이 자주 발생하는 문제점도 있었다.

따라서 기능을 중심으로 한 설계를 찾아보던 중 FSD(Feature Sliced Design)를 발견했다. FSD는 정형화된 구조가 있으며 객체지향프로그래밍의 개념을 프론트엔드에 적용해 볼 수 있는 설계였고 유지보수와 확장에도 좋은 설계라고 생각하여 도입하게 되었다. 하지만 개념이 너무 어렵고 당시에는 자료가 많지 않아 공부하는데 애를 먹었다..
이 글에서는 FSD의 개념을 간단히 설명하고 우리 프로젝트에 어떤식으로 적용을 했는지, 그리고 그 과정에서 느낀 점을 바탕으로 FSD를 적용하기 전에 고려해봐야 할 것들에 대해 정리해 보았다.
1️⃣ FSD의 개념
FSD(Feature Sliced Design)는 프론트엔드의 전통적인 모놀리스 구조에서는 파악하기 어려웠던 컴포넌트 간의 연결 관계와 모듈의 책임 불분명 등의 문제를 개선할 수 있다. FSD는 서비스를 6개의 계층으로 나눠 설계하는데, 이는 모듈의 응집력을 높이고 결합력을 낮춰 서비스의 유지보수와 확장이 용이하게 도울뿐만 아니라 객체지향 프로그래밍의 개념인 추상화, 상속, 캡슐화, 다형성을 적용해 볼 수도 있다.
이런 멋진 설계 방식을 도입하려면 우선 FSD의 기본 구조인 레이어(Layers), 슬라이스(Slices), 세그먼트(Segments)의 개념을 알아야 한다.

Layers
애플리케이션의 최상위 디렉터리로, 각 레이어마다 고유한 역할이 있으며 이 구조가 체계적인 모듈 분리를 할 수 있도록 한다.
- app: 애플리케이션의 진입점으로 ‘App.tsx’ 파일이 위치한다. 라우터나 전역스타일을 관리한다.
- pages: 애플리케이션의 페이지를 관리하는 계층이다. 전통적인 리액트 폴더 구조의 ‘pages’와 동일하다.
- widgets: 페이지에 사용되는 독립적인 UI 컴포넌트이다.
- features: 좋아요 버튼과 같은 사용자 관점의 비즈니스 기능을 관리한다.
- entities: 애플리케이션의 주요 도메인을 표현하는 계층으로, 도메인 관련 UI와 API를 정의한다.
- shared: 특정 비즈니스 로직에 종속되지 않은 재사용 가능한 컴포넌트나 유틸리티 등을 포함한다.
Layers의 중요한 특징은 상위 계층의 레이어는 하위 계층 레이어의 컴포넌트나 함수를 사용할 수 있지만, 하위 계층 레이어는 상위 계층 레이어의 컴포넌트나 함수를 사용할 수 없다는 점이다. 하위 계층으로 갈수록 모듈은 더 추상화되고 재사용성이 증가한다. 따라서 데이터 흐름이 한 방향으로만 진행되는 선형적인 구조를 유지할 수 있다.
Slices
각 레이어에는 하위 디렉터리인 슬라이스가 있다. 슬라이스는 각 레이어 내에서 비즈니스 엔티티에 따라 코드를 구조적으로 그룹화한다. 이를 통해 프로젝트의 복잡성을 효과적으로 관리하고 관련 코드를 논리적으로 조직화할 수 있다. 슬라이스의 이름은 애플리케이션의 요구사항에 맞춰 적절히 지정한다.
각 슬라이스 내부에는 ‘index.ts’와 같은 public API 파일이 있어야 한다. 이 파일에 외부로 내보낼 기능과 컴포넌트를 정의하며, 외부에서는 컴포넌트나 기능에 직접 접근하지 않고 이 파일을 통해 접근한다.
Segments
세그먼트는 각 슬라이스의 코드를 목적에 따라 나눈 디렉터리이다. 서버와의 통신을 담당하는 api, 슬라이스의 UI 컴포넌트를 포함하는 ui, 비즈니스 로직을 관리하는 model 등이 있다.
2️⃣ 적용 예시
FSD를 적용한 여러 프로젝트를 조사했지만, 프로젝트마다 구조를 나누는 기준이 달라 혼란스러웠다.😵💫 고민을 많이 하다 내린 결론은 개발이 복잡해지지 않도록 설계하기로…! 다만, 상위계층과 하위계층의 책임은 확실히 분리하여 객체지향 프로그래밍의 개념은 가져가기로 했다.
그래서 우리는 widgets과 feature 레이어를 생략하고 설계했다. feature를 entities에 넣고, widgets을 pages로 통합한 느낌(?)이다. 두 레이어를 생략한 이유는 모든 레이어를 전부 도입하면 서비스가 너무 분리되어 오히려 개발이 불편해질 것 같다는 의견이 있었기 때문이다. 진행 중인 프로젝트는 복잡한 서비스가 아니었기 때문에 대부분의 기능은 비즈니스 로직보다는 서버 데이터와 관련이 많았다. 따라서, entities와 features를 통합해도 괜찮을 것 같다고 판단했고 widgets도 굳이 분리할 필요성을 느끼지 못했다.
실제 프로젝트에 적용한 ‘프로젝트 조회 페이지의 댓글 기능’을 예시로 살펴보자 🧐
FSD의 모든 계층을 적용한 정석 폴더구조는 아래와 같을 것이다.
src/
├── app/
│ ├── App.tsx
├── pages/
│ ├── ProjectPage.tsx
├── widgets/
│ ├── Comments.tsx
├── features/
│ ├── comments/
│ │ ├── ProjectComments.tsx
│ │ ├── deleteComment.ts
│ │ ├── editComments.ts
│ │ ├── createComment.ts
├── entities/
│ ├── comments/
│ │ ├── ui/
│ │ │ ├── SingleComment.tsx
│ │ ├── api/
│ │ │ ├── comments.ts
│ │ ├── index.ts
하지만 우리는 features와 widgets을 제거했기 때문에 아래와 같아졌다.
src/
├── app/
│ ├── App.tsx
├── pages/
│ ├── Project/
│ │ ├── ui/
│ │ │ ├── Comments.tsx
│ ├── ProjectPage.tsx
├── entities/
│ ├── comments/
│ │ ├── ui/
│ │ │ ├── ProjectComments.tsx
│ │ │ ├── SingleComment.tsx
│ │ ├── api/
│ │ │ ├── comments.ts
│ │ ├── index.ts
각 파일은 아래와 같은 형식으로 구현되어 있다.
src/pages/ProjectPage.tsx
<Comments projectId={projectId} />
src/pages/project/ui/Comments.tsx
<ProjectComment
comments={data ? data.comments : []}
totalCount={data ? data.totalCount : 0}
projectId={projectId}
/>
src/entities/comments/ui/ProjectComments.tsx
<div>
<div>{totalCount}개의 댓글</div>
<input
placeholder="댓글을 작성하세요."
value={input}
onChange={(e) => {
setInput(e.target.value);
}}
/>
<button onClick={() => submitComment(input)}>
<span>댓글 작성</span>
</button>
</div>
<div>
{comments &&
comments.map((comment: Comment, index) => (
<SingleComment key={index} projectId={projectId} comment={comment} />
))}
</div>
3️⃣ 느낀 점
반은 성공, 반은 실패이다. 사실 완벽하게 FSD를 도입하지 못했고 개선할 점이 매우 많다. 코드를 어디까지 분리해야 할지 아직도 고민하고 있다. 게다가 팀원들끼리 개발 시간이 통일되어있지 않으니 각자 적용한 방식도 조금씩 달랐다.
하지만 처음으로 설계에 대해 오래 고민을 하면서 ‘모듈의 응집도를 높이고 결합력을 낮춘다’는 말을 이해한 것 같다. 그리고 무엇보다 개발 중에 병합 충돌이 거의 없었다! 앞으로는 계속 기능 중심으로 설계를 진행해보고 싶다..! 이번 경험은 ‘좋은 설계’와 ‘좋은 코드’에 대해 더욱 관심을 갖게 되는 좋은 계기였다.
4️⃣ FSD를 적용하기 전 고려해봐야 할 것들
1. 팀원들이 FSD를 적용할 수 있는 상황인가?
FSD는 개념이 복잡하고 단번에 이해하기 쉽지 않다. 아마 FSD를 도입하는 과정에 꽤 시간이 소요될 것이다. 따라서 팀원들이 이를 학습하고 적용할 준비가 되어 있는지 확인하는 것이 중요하다. 프론트엔드 프로젝트를 처음 한다면 절대 추천하지 않는다..🙄
2. FSD 이외의 다른 적절한 방안은 없는가?
FSD는 모든 프로젝트에 적합하지 않을 수 있다. 프로젝트의 복잡도와 요구사항에 따라 더 적절한 설계 방안이 있을 수 있기 때문에 FSD를 도입하기 전에 다른 설계 방법론을 검토해봐야 한다.
다른 설계 방법
✅ FDD(Feature Driven Development)
FSD와 비슷하게 기능 중심 설계에 초점을 맞추되, 좀 더 간단하게 폴더 구조를 설계하고 싶다면 FDD(Feature Driven Development) 방법론을 적용해 보는 것을 추천한다.
FDD는 도메인 중심 설계(DDD)에 뿌리를 둔 개발 방법론으로 기능 별로 폴더를 나누어 각 기능에 필요한 컴포넌트와 API호출, 상태관리를 관리한다. 따라서 FSD와 비슷하게 각 기능을 독립적으로 개발하고 테스트할 수 있다. 아래는 주문 기능에 대해 FDD를 적용한 예시이다.

✅ Atomic Design
만약 작은 컴포넌트들을 체계적으로 관리하여 재사용성을 높일 수 있는 UI 중심의 설계를 원한다면 Atomic Design 설계방식을 고려해 봐도 좋다. Atomic Design은 UI를 구성하는 요소들을 다섯 가지 계층으로 분류하여 체계적으로 컴포넌트를 관리할 수 있다. 아래는 주문 기능에 대해 Atomic Design을 적용한 예시이다.

완벽한 설계는 없다!
FSD 개념이 워낙 어려워서 설계에 애를 먹고 계신 분들에게 감히 팁을 드리자면…🐥 초기 설계를 바탕으로 개발 도중 계속 고민하며 개선해 나가자는 마음 가짐으로 진행해 보길 바란다! (초 주니어 개발자인)나 같은 경우 확실히 직접 코드를 짜면서 FSD 구조에 대한 감이 조금 더 잡혔다.
이미지, 글 참고
FSD 도입기
올해 3월 초에 실 서비스를 목적으로 프로젝트를 하나 기획했다. 서비스를 장기 운영할 목적으로 기획했기 때문에 유지보수와 확장이 용이하도록 프로젝트를 설계해야겠다고 판단했다.
계속 진행해 왔던 폴더 구조는 아래와 같이 화면 중심으로 설계를 했었다. 하지만 이 구조는 화면이 바뀔 경우 디펜던시가 많아 수정 작업이 많이 발생했고, 개발 도중 병합 충돌이 자주 발생하는 문제점도 있었다.

따라서 기능을 중심으로 한 설계를 찾아보던 중 FSD(Feature Sliced Design)를 발견했다. FSD는 정형화된 구조가 있으며 객체지향프로그래밍의 개념을 프론트엔드에 적용해 볼 수 있는 설계였고 유지보수와 확장에도 좋은 설계라고 생각하여 도입하게 되었다. 하지만 개념이 너무 어렵고 당시에는 자료가 많지 않아 공부하는데 애를 먹었다..
이 글에서는 FSD의 개념을 간단히 설명하고 우리 프로젝트에 어떤식으로 적용을 했는지, 그리고 그 과정에서 느낀 점을 바탕으로 FSD를 적용하기 전에 고려해봐야 할 것들에 대해 정리해 보았다.
1️⃣ FSD의 개념
FSD(Feature Sliced Design)는 프론트엔드의 전통적인 모놀리스 구조에서는 파악하기 어려웠던 컴포넌트 간의 연결 관계와 모듈의 책임 불분명 등의 문제를 개선할 수 있다. FSD는 서비스를 6개의 계층으로 나눠 설계하는데, 이는 모듈의 응집력을 높이고 결합력을 낮춰 서비스의 유지보수와 확장이 용이하게 도울뿐만 아니라 객체지향 프로그래밍의 개념인 추상화, 상속, 캡슐화, 다형성을 적용해 볼 수도 있다.
이런 멋진 설계 방식을 도입하려면 우선 FSD의 기본 구조인 레이어(Layers), 슬라이스(Slices), 세그먼트(Segments)의 개념을 알아야 한다.

Layers
애플리케이션의 최상위 디렉터리로, 각 레이어마다 고유한 역할이 있으며 이 구조가 체계적인 모듈 분리를 할 수 있도록 한다.
- app: 애플리케이션의 진입점으로 ‘App.tsx’ 파일이 위치한다. 라우터나 전역스타일을 관리한다.
- pages: 애플리케이션의 페이지를 관리하는 계층이다. 전통적인 리액트 폴더 구조의 ‘pages’와 동일하다.
- widgets: 페이지에 사용되는 독립적인 UI 컴포넌트이다.
- features: 좋아요 버튼과 같은 사용자 관점의 비즈니스 기능을 관리한다.
- entities: 애플리케이션의 주요 도메인을 표현하는 계층으로, 도메인 관련 UI와 API를 정의한다.
- shared: 특정 비즈니스 로직에 종속되지 않은 재사용 가능한 컴포넌트나 유틸리티 등을 포함한다.
Layers의 중요한 특징은 상위 계층의 레이어는 하위 계층 레이어의 컴포넌트나 함수를 사용할 수 있지만, 하위 계층 레이어는 상위 계층 레이어의 컴포넌트나 함수를 사용할 수 없다는 점이다. 하위 계층으로 갈수록 모듈은 더 추상화되고 재사용성이 증가한다. 따라서 데이터 흐름이 한 방향으로만 진행되는 선형적인 구조를 유지할 수 있다.
Slices
각 레이어에는 하위 디렉터리인 슬라이스가 있다. 슬라이스는 각 레이어 내에서 비즈니스 엔티티에 따라 코드를 구조적으로 그룹화한다. 이를 통해 프로젝트의 복잡성을 효과적으로 관리하고 관련 코드를 논리적으로 조직화할 수 있다. 슬라이스의 이름은 애플리케이션의 요구사항에 맞춰 적절히 지정한다.
각 슬라이스 내부에는 ‘index.ts’와 같은 public API 파일이 있어야 한다. 이 파일에 외부로 내보낼 기능과 컴포넌트를 정의하며, 외부에서는 컴포넌트나 기능에 직접 접근하지 않고 이 파일을 통해 접근한다.
Segments
세그먼트는 각 슬라이스의 코드를 목적에 따라 나눈 디렉터리이다. 서버와의 통신을 담당하는 api, 슬라이스의 UI 컴포넌트를 포함하는 ui, 비즈니스 로직을 관리하는 model 등이 있다.
2️⃣ 적용 예시
FSD를 적용한 여러 프로젝트를 조사했지만, 프로젝트마다 구조를 나누는 기준이 달라 혼란스러웠다.😵💫 고민을 많이 하다 내린 결론은 개발이 복잡해지지 않도록 설계하기로…! 다만, 상위계층과 하위계층의 책임은 확실히 분리하여 객체지향 프로그래밍의 개념은 가져가기로 했다.
그래서 우리는 widgets과 feature 레이어를 생략하고 설계했다. feature를 entities에 넣고, widgets을 pages로 통합한 느낌(?)이다. 두 레이어를 생략한 이유는 모든 레이어를 전부 도입하면 서비스가 너무 분리되어 오히려 개발이 불편해질 것 같다는 의견이 있었기 때문이다. 진행 중인 프로젝트는 복잡한 서비스가 아니었기 때문에 대부분의 기능은 비즈니스 로직보다는 서버 데이터와 관련이 많았다. 따라서, entities와 features를 통합해도 괜찮을 것 같다고 판단했고 widgets도 굳이 분리할 필요성을 느끼지 못했다.
실제 프로젝트에 적용한 ‘프로젝트 조회 페이지의 댓글 기능’을 예시로 살펴보자 🧐
FSD의 모든 계층을 적용한 정석 폴더구조는 아래와 같을 것이다.
src/
├── app/
│ ├── App.tsx
├── pages/
│ ├── ProjectPage.tsx
├── widgets/
│ ├── Comments.tsx
├── features/
│ ├── comments/
│ │ ├── ProjectComments.tsx
│ │ ├── deleteComment.ts
│ │ ├── editComments.ts
│ │ ├── createComment.ts
├── entities/
│ ├── comments/
│ │ ├── ui/
│ │ │ ├── SingleComment.tsx
│ │ ├── api/
│ │ │ ├── comments.ts
│ │ ├── index.ts
하지만 우리는 features와 widgets을 제거했기 때문에 아래와 같아졌다.
src/
├── app/
│ ├── App.tsx
├── pages/
│ ├── Project/
│ │ ├── ui/
│ │ │ ├── Comments.tsx
│ ├── ProjectPage.tsx
├── entities/
│ ├── comments/
│ │ ├── ui/
│ │ │ ├── ProjectComments.tsx
│ │ │ ├── SingleComment.tsx
│ │ ├── api/
│ │ │ ├── comments.ts
│ │ ├── index.ts
각 파일은 아래와 같은 형식으로 구현되어 있다.
src/pages/ProjectPage.tsx
<Comments projectId={projectId} />
src/pages/project/ui/Comments.tsx
<ProjectComment
comments={data ? data.comments : []}
totalCount={data ? data.totalCount : 0}
projectId={projectId}
/>
src/entities/comments/ui/ProjectComments.tsx
<div>
<div>{totalCount}개의 댓글</div>
<input
placeholder="댓글을 작성하세요."
value={input}
onChange={(e) => {
setInput(e.target.value);
}}
/>
<button onClick={() => submitComment(input)}>
<span>댓글 작성</span>
</button>
</div>
<div>
{comments &&
comments.map((comment: Comment, index) => (
<SingleComment key={index} projectId={projectId} comment={comment} />
))}
</div>
3️⃣ 느낀 점
반은 성공, 반은 실패이다. 사실 완벽하게 FSD를 도입하지 못했고 개선할 점이 매우 많다. 코드를 어디까지 분리해야 할지 아직도 고민하고 있다. 게다가 팀원들끼리 개발 시간이 통일되어있지 않으니 각자 적용한 방식도 조금씩 달랐다.
하지만 처음으로 설계에 대해 오래 고민을 하면서 ‘모듈의 응집도를 높이고 결합력을 낮춘다’는 말을 이해한 것 같다. 그리고 무엇보다 개발 중에 병합 충돌이 거의 없었다! 앞으로는 계속 기능 중심으로 설계를 진행해보고 싶다..! 이번 경험은 ‘좋은 설계’와 ‘좋은 코드’에 대해 더욱 관심을 갖게 되는 좋은 계기였다.
4️⃣ FSD를 적용하기 전 고려해봐야 할 것들
1. 팀원들이 FSD를 적용할 수 있는 상황인가?
FSD는 개념이 복잡하고 단번에 이해하기 쉽지 않다. 아마 FSD를 도입하는 과정에 꽤 시간이 소요될 것이다. 따라서 팀원들이 이를 학습하고 적용할 준비가 되어 있는지 확인하는 것이 중요하다. 프론트엔드 프로젝트를 처음 한다면 절대 추천하지 않는다..🙄
2. FSD 이외의 다른 적절한 방안은 없는가?
FSD는 모든 프로젝트에 적합하지 않을 수 있다. 프로젝트의 복잡도와 요구사항에 따라 더 적절한 설계 방안이 있을 수 있기 때문에 FSD를 도입하기 전에 다른 설계 방법론을 검토해봐야 한다.
다른 설계 방법
✅ FDD(Feature Driven Development)
FSD와 비슷하게 기능 중심 설계에 초점을 맞추되, 좀 더 간단하게 폴더 구조를 설계하고 싶다면 FDD(Feature Driven Development) 방법론을 적용해 보는 것을 추천한다.
FDD는 도메인 중심 설계(DDD)에 뿌리를 둔 개발 방법론으로 기능 별로 폴더를 나누어 각 기능에 필요한 컴포넌트와 API호출, 상태관리를 관리한다. 따라서 FSD와 비슷하게 각 기능을 독립적으로 개발하고 테스트할 수 있다. 아래는 주문 기능에 대해 FDD를 적용한 예시이다.

✅ Atomic Design
만약 작은 컴포넌트들을 체계적으로 관리하여 재사용성을 높일 수 있는 UI 중심의 설계를 원한다면 Atomic Design 설계방식을 고려해 봐도 좋다. Atomic Design은 UI를 구성하는 요소들을 다섯 가지 계층으로 분류하여 체계적으로 컴포넌트를 관리할 수 있다. 아래는 주문 기능에 대해 Atomic Design을 적용한 예시이다.

완벽한 설계는 없다!
FSD 개념이 워낙 어려워서 설계에 애를 먹고 계신 분들에게 감히 팁을 드리자면…🐥 초기 설계를 바탕으로 개발 도중 계속 고민하며 개선해 나가자는 마음 가짐으로 진행해 보길 바란다! (초 주니어 개발자인)나 같은 경우 확실히 직접 코드를 짜면서 FSD 구조에 대한 감이 조금 더 잡혔다.
이미지, 글 참고