컴포넌트, API, 페이지 등을 역할별로 분리하여 관리했고, 당시에는 충분히 확장 가능하다고 판단했다.
하지만 기능이 추가될수록 새로운 파일을 어디에 배치해야 할지 고민하는 상황이 반복적으로 발생했고,
그때마다 명확한 기준이 없었기 때문에 필요할 때마다 새로운 폴더를 생성하거나 기존 구조를 수정하며 대응했다.
결국 개발 후반부에는...

위처럼 역할별 폴더가 점점 증가하였다... 다시 봐도 아찔하다....
처음부터 참여한 나는 파일의 위치를 기억하고 있었기 때문에 원하는 코드를 빠르게 찾을 수 있었지만, 다른 개발자가 프로젝트에 참여한다면 파일을 찾는 데 적지 않은 시간이 필요했을 것 같다.
즉, 프로젝트 구조가 내 머릿속에만 존재하는 규칙에 의존하고 있었다.
해결해야할 과제는 다음과 같았다.
단순히 폴더 정리하는 것을 넘어 '나만의 코드 분리 기준과 아키텍처 설계 역량 키우기' 를 목표로 설정했다.
마침 현업 개발자에게 프로젝트 리뷰를 받을 기회가 있었는데, 그 과정에서 FSD(Feature-Sliced Design) 아키텍처를 추천받았다.
관심이 생겨 공식 문서를 살펴보던 중 다음 문장을 보게 되었다.
요구사항이 바뀌어도 코드 구조가 무너지지 않고,
새 기능을 쉽게 추가할 수 있는 프로젝트를 만드는 것
내가 겪고 있던 문제 역시 프로젝트가 성장하면서 기존 구조가 점점 복잡해지고, 파일의 위치를 결정하기 어려워졌다는 점이었다.
FSD가 추구하는 방향이 현재 프로젝트의 문제 상황과 일치한다고 느꼈고, 이번 리팩토링을 통해 학습한 뒤 다음에도 적용해 보기 위해 도입을 결정했다.
기존 프로젝트는 컴포넌트, 훅, API와 같이 기술적인 역할을 기준으로 코드를 분리하고 있었다.

처음에는 직관적으로 보였지만 프로젝트가 커질수록 특정 기능을 수정하기 위해 여러 폴더를 탐색해야 했고, 관련 코드가 흩어져 있어 변경 범위를 파악하기 어려웠다.
반면 FSD는 기술 중심이 아닌 기능 중심으로 코드를 구성한다.

이를 통해 관련 코드들을 같은 영역에 배치할 수 있고, 프로젝트가 커져도 구조를 예측하기 쉬워진다.
🔍 공식문서 핵심만 읽고 다음단계를 시작했다.
- FSD란?
- Layer
- Slice
- Public API
- 점진적 도입
기존 기능이 정상적으로 동작하는 상태를 유지하면서 점진적으로 구조를 변경하는 방식을 선택했다.
공식 문서 마이그레이션 가이드는 다음과 같이 먼저 정리하기를 추천한다.

가장 먼저 Shared 레이어부터 정리했는데, 특정 비즈니스 로직에 의존하지 않는 코드를 모아두는 영역이다.

| 세그먼트(Segment) | 포함된 내용 |
|---|---|
| api | axios instance (interceptor 포함), 전역 로딩 및 에러 처리 커스텀 훅 |
| assets | 폰트, 공통 이미지 등 에셋 |
| lib | 유틸리티 함수들 |
| ui | 헤더 등 범용적으로 재사용되는 UI 컴포넌트 |
이 과정에서는 재사용 가능한 코드인지를 기준으로 위치시키려 하였다.
Shared 레이어 정리가 끝난 후에는 Pages 레이어를 정리하기 시작했다.
다음은 기존의 pages 폴더 구조이다.

단순히 페이지 컴포넌트만 두고 components, hooks 등의 폴더 요소를 조립만 하는 구조였다.
리팩토링을 통해 각 페이지 슬라이스의 네이밍 컨벤션을 맞추고, 세그먼트를 구조화했다.

| 세그먼트(Segment) | 포함된 내용 |
|---|---|
| api | 해당 페이지 전용 백엔드 요청 함수 |
| model | 해당 페이지 상태 관리, 커스텀 훅, 상수 |
| ui | 해당 페이지 전용 UI 컴포넌트 |
Shared, Pages 구조를 정리한 후에는 Public API를 적용했다.
이전에는 파일을 직접 import하는 경우가 많았다.
// Before: 내부 구조가 외부로 노출됨
import Header from "@/shared/ui/header/Header";
이 방식은 내부 구조가 변경되면 모든 import 경로도 함께 수정해야 한다는 문제가 있다.
그래서 각 폴더에 index.js를 생성하여 노출할 파일만 export하고, 외부에서는 Public API만 접근하도록 변경했다.
// share/ui/header/index.js
export { default as Header } from "./Header";
이후에는 아래와 같이 사용할 수 있게 되었다.
// After: 내부 구조 캡슐화
import { Header } from "@/shared/ui/header";
이를 통해 내부 구현을 숨기고 의존성을 관리할 수 있게 되었다.
새로운 아키텍처를 적용하며 구조를 정리하던 중 예상하지 못한 문제가 발생했다.
Context 관련 코드에서 순환 참조가 발생한 것이다.
처음에는 단순히 폴더를 이동하는 작업이라고 생각했지만, FSD 아키텍처가 "상위 레이어는 하위 레이어를 참조할 수 없다"는 단방향 의존성 규칙을 강조하는지 이유를 알 수 있었다.
해당 내용은 별도의 글로 정리해볼까 한다.
| 비교 항목 | 리팩토링 전 (Before) | FSD 아키텍처 적용 후 (After) |
|---|---|---|
| 분리 기준 | 기술 기준 | 비즈니스 관심사 및 기능 기준 |
| 코드 배치 고민 | 매번 새로운 파일 위치 고민 | 규칙에 따라 즉시 결정 |
| 의존성 관리 | 내부 파일에 직접 접근하여 결합도 증가 | Public API를 통한 결합도 완화 및 캡슐화 |
| 확장성 | 기능 추가 시 무분별한 폴더 확장 | 필요한 레이어만 변화 가능 |
기존 구조는 역할별로 코드를 분리하고 있었다.

초기에는 직관적으로 보였지만 프로젝트 규모가 커질수록 관련 코드가 여러 폴더에 흩어지게 되었다.
또한 새로운 기능이 추가될 때마다 폴더 구조가 계속 확장되었고, 파일의 위치를 판단하는 기준도 점점 모호해졌다.
무엇보다 프로젝트를 처음 접하는 사람이 이해하기 어려운 상태였다.
FSD 적용 후에는 레이어별 역할이 명확해졌다.

현재 프로젝트 규모에서는 Widgets, Features, Entities 레이어까지 분리할 필요성을 느끼지 못했기 때문에 우선 필요한 레이어만 적용하였다.
또한 Shared와 Pages 레이어에 Public API를 적용하여 외부에서 내부 구현에 직접 접근하지 않도록 구조를 개선했다.
이를 통해 내부 폴더 구조가 변경되더라도 영향 범위를 최소화할 수 있게 되었다.
이번 리팩토링을 진행하면서 가장 크게 느낀 점은 아키텍처는 단순히 폴더를 보기 좋게 정리하는 작업이 아니라는 것이다.
처음에는 폴더 구조만 변경하면 될 것이라고 생각했지만, 실제로는 의존성 방향을 설계하고 각 레이어의 책임을 명확하게 이해하는 과정이 더 중요했다.
특히 Context 관련 로직을 분리하는 과정에서 순환 참조 문제가 발생하면서, 그 사실을 더 크게 체감할 수 있었다.
현재 프로젝트에서는 App, Pages, Shared 레이어만으로도 충분히 구조를 개선할 수 있어서 더 나누진 않았지만, 이를 시작으로 앞으로 참여하는 프로젝트의 규모에 맞춰 다른 Layer도 구성해보며 아키텍처 구조 설계에 대한 능력을 더욱 키워야 겠다고 생각했다.
아직도 완벽한 구조라고 생각하지는 않지만, 이번 경험을 통해 코드의 책임을 나누고 의존성을 관리하는 방법에 대해 이전보다 더 깊게 고민할 수 있었다.
다음 프로젝트에서는 처음부터 FSD를 적용하여 어떤 차이가 있는지 경험해 볼 예정이다.
퍼가요~^^