1. 코드 구조에 대해: FSD
프로젝트가 장기적으로 진행되다 보면 하나의 파일에 코드가 너무 길어질 때가 있다. 한 이틀만 안 봐도 내가 쓴 코드를 다시 읽는게 굉장히 어렵다. 그런 순간에 파일 구조 분리의 필요성을 느끼는데, 프론트는 정해진 구조가 딱히 없는 것 같다. 여태까지는 대충 되는대로 분리해서 썼는데, 파일 구조에 대한 방법론들이 있다고 들었다.
그 중 괜찮아 보이는걸 가져와서 한 번 공부해보고자 한다.
FSD(Feature Sliced Design)
FSD는 기능 중심 아키텍쳐이다.
/src /assets # 이미지, 폰트 등 /components # UI 컴포넌트 /constants # 상수 /hooks # 커스텀 훅 /models # 유틸리티 함수 /services # API 호출 /utils # 유틸리티 함수
일반적으로는 용도에 따라 구분해서 이런 식으로 많이 사용한다. 각 폴더의 이름만 봐도 어떤 역할을 하는 지 감이 잡히기 때문에 흔히 사용하는 구조이다.
그러나 한 가지 문제점이 있다.
만들어놓은 하나의 기능을 수정할 때 찾아봐야 할 폴더가 너무 많다는 것이다. 하나의 기능에 코드들이 흩어져 있기 때문에, 각 파일 간의 상관관계를 파악하는 것이 중요한데, 이것이 쉽지 않은 일이다. 잘못 만지면 어디서부터 잘못 되었는지 파악하기가 어렵다.
지금 만들어놓은 블로그의 폴더 구조가 딱 그렇다. 리팩토링 할 때 마다 한숨부터 나온다(...)
FSD의 구조
(이게 뭔가요...)
일단 FSD는 기능(Feature) 을 중심으로 파일을 구성한다. 하나의 기능 단위로 필요한 모든 파일들을 같은 폴더에 모아 관리하는 것을 목표로 한다. 이론적으로만 들으면 매우 괜찮은 방법인 것 같은데, 위의 그림은 무엇일까?
FSD는 계층 구조를 가진다. 크게 레이어 / 슬라이스 / 세그먼트로 나뉜다. 폴더 구조의 최상위에는 레이어가 위치하고, 각 레이어는 슬라이스를 포함하며, 각 슬라이스는 또한 세그먼트를 포함한다.
레이어는 여러개의 층으로 쌓여 있는데, 현재 process 는 사용되지 않는다고 한다.
Layers
1. App 레이어
프로젝트의 루트 구성과 초기 설정을 하는 레이어이다. 라우팅, 글로벌 스타일, 최상위 프로바이더 등을 포함한다.
2.Pages 레이어 (Process 는 현재 사용 x 이니 패스)
앱 내 개별 페이지를 정의하는 레이어이다. 라우팅 관련 파일이나, 페이지에 해당하는 최상위 컴포넌트를 포함한다. 아무래도 바로 노출되는 컴포넌트이다 보니, 복잡한 비즈니스 로직을 포함하지 않는다.
3. Widgets 레이어
페이지 내에서 독립적으로 작동하는 큰 기능 단위를 포함하는 레이어이다.
다양한 페이지에서 재사용할 수 있다. Header Sidebar Footer 같은 것들에 해당한다.
4. Features 레이어
재사용 가능한 비즈니스 로직을 위한 레이어이다.
글쓰기 좋아요 기능 등과 같이 다양한 페이지와 위젯에서 재사용 가능하도록 해야 한다.
그렇게 해야 확장성과 유지 보수성이 늘어난다.
5. Entities 레이어
비즈니스의 실제 데이터 단위를 나타내는 레이어이다.
주로 데이터 모델과 해당 데이터에 대한 로직이 포함된다. 예를 들어User 라는 엔터티에는 사용자의 이름, 이메일, 가입날짜 등의 정보를 관리해야한다.
6. Shared 레이어
프로젝트 전반적으로 재사용할 수 있는 유틸리티, 기본적인 컴포넌트, 스타일 등을 포함하는 레이어이다.
특정 비즈니스 로직에 국한되지 않고 전반적으로 사용된다.
Button 같은 컴포넌트를 만들어 두면 다양한 페이지와 위젯에서 재사용할 수 있다.
순수하게 재사용 가능한 함수도 이곳에 포함된다.
레이어 참조 규칙
FSD는 단방향 의존성 규칙을 따른다. 레이어는 자신의 자식 레이어의 구성 요소만 참조하거나 임포트할 수 있다.
(같은 레이어 끼리도 참조나 임포트가 불가능하다.)
위의 사진을 다시 보면서 말하자면, Features 레이어는 Shared나 Entities 레이어의 구성 요소는 참조할 수 있지만, 상위 레이어인 Widgets레이어의 구성 요소는 참조할 수 없다.
이의 이점은 코드의 구조가 명확해지며, 계층간의 결합도 가 낮아져, 특정 레이어에서 발생한 변경이 다른 레이어로 확산되지 않도록 방지한다.
일반적인 코드 구조에서는 하나의 파일을 바꾸면 그와 관련없는 기능이 망가질 때도 있는데, 이를 방지하기 위한 체계인 것 같다. (어쩐지 네트워크의 OSI 7계층 모델이 떠오른다)
레고 블록을 쌓는 모습이 생각나는 구조이기도 하다.
Slices
각 레이어는 슬라이스라는 하위 디렉토리를 지닌다. 이는 비즈니스를 도메인 별로 구분하는 역할을 한다.
각 비즈니스 도메인 별로 관련된 코드를 모아, 유지보수에 용이하도록 만든다.
블로그 프로젝트라고 치면 게시글, 댓글, 유저데이터 이것이 각각의 Slice가 될 것이다.
게시글 슬라이스에서는 게시글 데이터 타입 정의, 상세 페이지용 UI 컴포넌트 등이 있을 것이다.
한 번에 이해하기 쉽지 않다.
마찬가지로 각 슬라이스 또한 같은 레이어 내에서 다른 슬라이스를 참조할 수 없다.
각 비즈니스 도메일 별로 독립적으로 관리하기에 해당 도메인에 집중할 수 있도록 하는 것이 목적인 것 같다.
게시글과 댓글 슬라이스는 서로 영향을 주지 않아야 한다. 이는 전체 시스템의 안정성을 높인다.
Segments
또한 슬라이스 내에는 기능별로 분리된 세그먼트들이 존재한다.
예를 들어 블로그의 게시글 기능인 Post 슬라이스에서는 상태관리를 담당하는 model, 컴포넌트를 담당하는 ui, 서버와 요청을 주고 받는 api 등이 있을 것이다.
결국은 세그먼트도 각 코드를 구조적으로 분리해, 특정 세그먼트만 수정해 유지보수가 가능하다.
src/ ├── app/ # 1. 앱의 심장 (설정) │ ├── providers/ # QueryClient, AuthProvider 등 │ ├── styles/ # 전역 CSS (main.css) │ └── router/ # AppRouter.tsx │ ├── pages/ # 2. 큰 도화지 (페이지 단위) │ ├── post-detail/ # 게시글 상세 페이지 │ └── write-page/ # 글 작성 페이지 │ ├── widgets/ # 3. 큰 덩어리 (조합된 기능) │ ├── header/ # 로고 + 네비게이션 + 유저정보 │ └── post-comments/ # 댓글 리스트 + 댓글 작성창 (Entity + Feature 조합) │ ├── features/ # 4. 사용자의 행동 (액션) │ ├── add-comment/ # 댓글 등록 버튼 및 전송 로직 │ ├── delete-post/ # 게시글 삭제 기능 │ └── auth-by-email/ # 이메일 로그인 로직 │ ├── entities/ # 5. 데이터의 단위 (실체) │ ├── post/ # 게시글 데이터 타입, PostCard 컴포넌트 │ ├── comment/ # 댓글 데이터 타입, CommentItem 컴포넌트 │ └── user/ # 유저 정보 데이터 │ └── shared/ # 6. 공통 도구 (부품) ├── ui/ # Button.tsx, Input.tsx (순수 UI) ├── api/ # axiosInstance.ts └── lib/ # formatDate.ts, useDebounce.ts
파일구조는 대략 이렇게 생겼다.
정리
- 기능 중심 코드 분할 : 코드를 기능단위로 나뉘어 관리하기에 비즈니스 요구사항이 변경되어도 특정 기능만 집중해서 수정할 수 있다.
- 단방향 의존성: 상위 레이어만 하위 레이어를 참조할 수 있기에, 코드의 결합성을 낮추고, 독립성을 갖추어 유지보수성을 높인다.
이로 인해 얻게 되는 이점이
- 코드 가독성 및 관리 용이성
- 확장성
- 유지 보수성
등이 있다. 이론적으로만 봤을 때는 괜찮은 아키텍쳐인 것 같다. 하지만 실제로 사용해봤을 때 느끼는 점이 있을테니, 다음 프로젝트에 한 번 도입해보는 식으로 해보면 좋을 것 같다.
2026년 3월 25일 AM 8:25
로그인 필요