우선, 이 글은 실무에서 직접 FSD를 운영해본 경험에 대한 글이 아니다. 주니어 개발자로서 사이드 프로젝트에 적용하며 겪은 시행착오, 주변 실무자들에게 들은 경험담, 공개 글/공식 문서에서 읽은 규칙들을 바탕으로 정리해본 글이다. 정답을 정의하는 것보다 내 프로젝트에 맞게 어떻게 어디까지 가져올지 판단하는 기준을 고민하며 기록해보았다. FSD 활용에 관한 하나의 의견으로 봐주었으면 좋겠고, 틀린 부분에 대한 피드백은 언제든 환영이다.
결론부터 말하면, FSD를 풀세트로 도입하는 게 아닌 FSD의 핵심 철학을 가져와 프로젝트에 맞게 축소 적용하는 법을 위주로 정리해보려고 한다.
리액트 스타터킷에서 흔히 보는 구조는 components / hooks / utils / styles 같은 역할별 분류이다. 이 구조는 파일 성격만 보고 폴더를 정하면 되기 때문에, 작은 규모에서는 만들기 쉽고 탐색도 빠르다.
문제는 프로젝트가 커질 때부터다. “훅”이라는 형태보다 “어느 기능에 속하는지”가 더 중요해진다. 예를 들어 hooks/ 폴더 안에 로그인/뉴스/북마크 훅이 한데 섞이면, 기능 하나를 수정할 때 관련 코드를 찾기 위해 폴더를 여러 번 횡단하게 된다. 이때부터는 도메인(무엇을 다루는가), 유즈케이스(사용자가 무엇을 하는가), 데이터 흐름(어디서 오고 어디로 가는가) 같은 관점이 얽히면서 구조가 더 빨리 복잡해진다.
그 결과 아래와 같은 증상이 나타난다.
단순히 폴더를 간결하게 정리하는 것보다, 기능 단위로 변경 영향 범위를 줄이는 구조의 필요성을 느끼게 되었다.
FSD(Feature-Sliced Design)는 프론트엔드 애플리케이션을 Layers / Slices / Segments로 구조화하는 방법론이다. 더 자세한 내용은 FSD 공식 가이드에 잘 나와있고, 이 글에서는 FSD의 핵심이 되는 두 가지 제약을 정리해보려고 한다.
co-location은 기능 구현에 필요한 UI/상태/요청/유틸을 가깝게 두어, 기능 단위 탐색을 쉽게 만드는 배치 전략이다. 역할별 폴더에서는 UI는 components, 요청은 api, 상태는 store와 같이 구조가 흩어지게 된다. 반면 co-location에서는 기능 폴더 안에서 대부분의 코드 탐색이 끝나게 만든다.
예를 들면 로그인 기능이라면 아래처럼 배치를 구성하는 것이다.
📂 features
└─ 📂 login
├─ 📂 api
├─ 📂 lib
├─ 📂 model
└─ 📂 ui
로그인 기능에 대한 수정이 필요할 때, 확인해야 하는 위치가 명확해진다. 이 단순함이 생각보다 강력하게 탐색 비용을 줄여준다.
FSD는 상위 레이어 → 하위 레이어로만 의존 가능하다는 단방향 의존성 규칙이 있다. 여기서 더 중요한 포인트는, 같은 레이어 안에서도 슬라이스끼리 내부 파일을 직접 import하지 못하게 막는 것이다.

A 기능이 B 기능의 내부 파일을 직접 import하지 못하게 하면,
이 제약이 실제로 높은 응집도와 낮은 결합도를 만들게 된다.
예를 들어 features/news가 features/stock 내부 파일을 직접 import할 수 없게 하면, stock의 폴더 구조를 바꾸거나 내부 구현을 갈아엎어도 ‘외부에서 접근하는 공개된 API(예: index.ts)’만 유지하면 된다. 결과적으로 변경은 한 슬라이스 경계에서 멈추고, 영향 범위가 예측 가능해지게 된다.

FSD의 정석 레이어는 App / Pages / Widgets / Features / Entities / Shared으로 구성된다.
| Layer | 역할 | 메모 |
|---|---|---|
| App | 엔트리/라우팅/프로바이더/전역 설정 | 대부분 유지 |
| Pages | 라우트 단위 화면 | features로 흡수되기도 함 |
| Widgets | 화면을 구성하는 큰 UI 블록 | 기준이 흔들리기 쉬워서 종종 생략 |
| Features | 사용자 행동/유즈케이스(동사) | 가장 핵심이 되는 레이어 |
| Entities | 도메인 모델(명사) | 도메인 모델이 커질 때 필요 |
| Shared | 공용 UI/유틸/리소스 | 망가지기 쉬움 |
최소 구조로 FSD를 적용할 때 사용하는 건, App과 Features 레이어이다. Pages, Widgets, Entities는 팀마다 다르게 쓰거나 덜 쓰는 경우가 많다.
어떻게 폴더를 구성하여 사용하는지에 따라 레이어가 다른 레이어의 하위로 갈수도, 생략할 수도, 추가될 수도 있다. 중요한 건, FSD를 사용하는 데 정답은 없다는 것이다. 팀과 프로젝트의 성격과 목적에 맞게 판단하여 유연하게 사용하는 것이 가장 중요하다.
FSD를 풀세트로 도입하면 구조는 일관되지만, 컨벤션 비용이 급격히 올라간다. 특히 팀 규모가 크지 않거나, 합의가 어렵거나, MVP 속도가 중요한 환경에서는 도입 비용이 바로 병목이 된다. 그래서 현업에서는 축소형이 자주 등장한다. 내가 사이드 프로젝트를 진행하며 가장 현실적인 시작점으로 차용한 방법은 features 위주의 최소 조합 방식이었다.
가장 현실적인 시작점이다. 기능 단위로 co-location을 확보하고, 나머지 레이어는 필요해질 때 강화한다. 예시는 다음과 같다.
📂 src
├─ 📂 app
│ ├─ 📂 providers
│ ├─ 📂 router
│ └─ 📂 styles
├─ 📂 features
│ ├─ 📂 news
│ │ ├─ 📂 api
│ │ ├─ 📂 lib
│ │ ├─ 📂 model
│ │ └─ 📂 ui
│ └─ 📂 stock
│ ├─ 📂 api
│ ├─ 📂 model
│ └─ 📂 ui
└─ 📂 shared
├─ 📂 assets
├─ 📂 lib
└─ 📂 ui
이 구조의 장점은 탐색 비용이 낮고, 기능 단위로 변경 영향 범위를 예측하기 쉽다는 점이다. 단점은 도메인 모델이 커지면(예: User/Ticker 규칙이 여러 기능에 흩어짐) 중복이 생기기 시작하며, 이 시점부터 entities 레이어가 필요해질 수 있다는 점이다.
해당 방식을 출발점으로 삼아 프로젝트가 커질 수록 상황에 맞게 확장하며 규칙을 강화해야 한다.
shared는 어디서든 참조 가능하다는 성질 때문에 기준 없이 여러 파일들이 들어가는 쓰레기통이 되기도 한다. 애매한 파일이 몽땅 들어간 복잡한 폴더가 되어 도메인 의미가 섞이고 경계(boundary)가 무너지면서 변경 영향 범위가 넓어지게 되는 것이다.

이러한 shared 쓰레기통 현상을 막기 위해서, shared 사용 규칙을 정의해보았다.
다음 조건을 모두 만족할 때만 shared를 허용하는 방식이다.
실제 예시를 들자면 아래와 같다.
공용처럼 보이는 코드가 사실은 특정 기능 내부에서만 재사용되는 경우가 많다. 이 경우 shared로 올리기 전에 기능 내부에서 먼저 해결한다.
📂 features
└─ 📂 news
├─ 📂 shared
├─ 📂 ui
└─ 📂 model
이 방식은 shared로 새는 코드를 줄이고, 도메인 결합을 유지한다.
public API는 보통 index.ts를 의미한다. 외부로 노출할 것만 export해 경계를 만들고 내부 구현을 감추는 방식이다.
장점은 명확하다.
다만 작은 프로젝트에서 모든 slice마다 index.ts를 만들면 오히려 운영 비용이 늘어날 수 있다. export를 계속 유지·정리해야 하고, 경계가 아직 안정되지 않은 시기에 API를 먼저 굳히면 변경 속도가 떨어질 수 있다.
경계가 필요한 곳만 public API를 둔다.
예: toast/modal 같은 전역 사용 기능, 여러 feature가 의존할 수밖에 없는 모듈
shared는 shared/index.ts만 노출하고 내부 폴더 직접 import를 금지해서, shared가 외부에 드러나는 범위(=public API surface)를 작게 유지한다.
public API는 만능이 아니라, 경계를 운영할 의지가 있을 때만 효과가 나는 장치이다.
직접 적용한 구조보다, 판단에 대한 로그를 작성해보았다.
주변 실무자에게 들은 이야기를 종합하면, 아래 같은 환경에서는 풀세트보다 features 중심 축소형이 선택되는 경우가 많다고 한다.
지금 시점에 감당 가능한 규칙의 무게에 따라 가능한 만큼의 FSD 방식을 우선 도입하는 것이다. FSD의 핵심 철학인 co-location에 따라 features를 메인으로 나누어 폴더를 구성한다. 그리고 필수 규칙부터 시작한 후, 단계적으로 필요에 따라 강화해나가는 전략이다.
다음 프로젝트에서 FSD를 어떻게 사용하면 좋을지에 대해 체크리스트 형태로 정리해보았다.
결론은 단순하다. 풀세트 FSD가 아니라 우리 팀이 감당 가능한 규칙의 무게로 시작한다. features-only로 출발하고, shared가 커지기 시작하면 규칙을 강화한다.