현재 진행중인 프로젝트 프론트 개발을 5월부터 시작하여 6개월 정도의 기간을 지속하다 보니까, 처음에 짜놓은 폴더구조에 대해서 불편함을 느끼기 시작했다. mvp 완성에 급급하여 마구잡이로 컴포넌트와 코드를 배치시켜서 다시 개발을 시작하려보니 집중이 안되는 그런느낌?
이대로 계속 가다가는 걷잡을 수 없이 지저분해질 것 같아서 우연히 봤던 기능(feature)을 단위로 모듈화하는 아키텍처 패턴이 인상적이라 적용해보고자 한다!!
FSD 아키텍처가 무엇인가
Feature-sliced Design(FSD) 구조는 프론트엔드 애플리케이션을 기능(Feature) 단위로 나누어 모듈화하고, 코드의 재사용성과 유지 보수성을 높이기 위한 아키텍처 패턴이다. 기존 많이 사용되던 컴포넌트 기반 구조보다 기능과 도메인에 초점을 맞추는 패턴이다.
[자세한 FSD 관련 글]
2024.10.04 - [프론트엔드 기록/React] - [Next.js 14] FSD 기능 관점 폴더 아키텍처에 대한 생각
폴더 구조 예시
src/
|-- app/ # Next.js의 라우팅 및 페이지 엔트리 포인트
|-- features/ # 기능별 모듈 (예: login, dashboard)
|-- entities/ # 도메인 엔티티 (예: User, Product)
|-- shared/ # 공통 컴포넌트, 유틸리티, 스타일
|-- widgets/ # 여러 기능을 조합한 UI 컴포넌트 (예: Header, Footer)
|-- processes/ # 여러 기능에 걸친 비즈니스 로직 및 플로우
현재 프로젝트 상태
현재 진행중인 프론트엔드 폴더구조는 컴포넌트 단위로 나누어 모듈화를 했다.
/app 폴더에 대한 페이지 기준으로 /component/@ 폴더에 페이지 관련 컴포넌트들을 모아놓았다. 머 간단한 방법이라고 생각할 수 있지만 , 이보다 더 많은 기능들을 모아놓았을때 너무 많은 폴더들이 너무 보기 싫을 것 같은 느낌이 들었다.
FSD 아키텍처로 리팩토링 해보자!
한번에 전체 구조를 변경하는 것보다 차근차근 기능(Feature) 단위로 모듈화를 해보자! 우선 이 포스트에서는 로그인 관련 기능으로 모듈화 해보려고 한다.
src / app
src/
|-- app/
| |-- login/
| | |-- page.tsx (로그인 페이지 엔트리)
| | |-- layout.tsx (로그인 페이지에만 적용할 레이아웃 - 선택 사항)
| |-- dashboard/
| | |-- page.tsx (대시보드 페이지 엔트리)
| | |-- layout.tsx (대시보드 전용 레이아웃)
| |-- not-found.tsx (404 페이지)
| |-- layout.tsx (전역 레이아웃)
| |-- page.tsx (홈 페이지)
app 디렉토리는 사실 딱히 건들것이 없다.
src/feature/login
src/
|-- features/
| |-- login/
| | |-- components/
| | | |-- LoginForm.tsx
| | | |-- GoogleRedirectButton.tsx
| | | |-- KakaoRedirectButton.tsx
| | | |-- NaverRedirectButton.tsx
| | |-- api/
| | | |-- loginApi.ts (API 호출 관련 로직)
| | |-- model/
| | | |-- useLogin.ts (로그인 로직 및 훅)
| | | |-- authSlice.ts (Redux 또는 Zustand와 같은 상태 관리 파일)
| | |-- pages/
| | | |-- LoginPage.tsx (로그인 페이지)
| | |-- index.ts
login feature안에서도 여러가지 부분으로 나뉘게 되는데 아래와 같이 나눌 수 있습니다.
- components : 로그인 기능과 관련된 UI 컴포넌트, 예를들면 로그인 폼, 소셜 로그인 버튼 등
- api : API 호출 로직을 모아놓음
예시) useLogin.ts
export const login = async (email: string, password: string) => {
try {
const response = await fetch('/api/login', {
email,
password,
});
// 서버에서 받은 데이터
const { data } = response;
// 성공적으로 로그인한 경우 데이터를 반환
return data;
} catch (error) {
// 에러 핸들링
}
};
- model : 상태 관리와 도메인 모델
- 로그인 상태를 Zustand 또는 Recoil과 같은 상태 관리 라이브러리를 사용하여 관리하는 경우 이곳에 보관
- 도메인 모델이나 로그인 유효성 검증 로직도 여기에 포함.
- hooks : 로그인과 관련된 커스텀 훅을 모아놓음.
- styles : 관련된 스타일 파일을 모아두는 폴더
공용 컴포넌트는 어디에?
FSD 구조에서 공통된 버튼이나 Input 같은 재사용 가능한 UI 컴포넌트는 shared 폴더 아래에 위치합니다. 공통으로 사용될 수 있는 컴포넌트, 유틸리티, 스타일 등을 관리합니다.
src/
|-- shared/
| |-- ui/
| | |-- Button.tsx
| | |-- Input.tsx
| | |-- Modal.tsx
| |-- hooks/
| | |-- useFetch.ts
| |-- styles/
| | |-- globals.css
data, type ...들은 어디에?
FSD 구조에서 타입과 데이터를 기능별로 맵핑할지 아니면 공통적으로 관리할지에 따라서 shared 폴더에 위치시킬 지 features/model 폴더에 위치시킬지를 결정합니다.
- 여러 기능에서 공통적으로 사용되는 타입이나 데이터는 shared 폴더에 배치하는 것이 좋습니다.
- 특정 기능에만 종속적인 타입과 데이터는 해당 기능 폴더에 배치하여 기능별로 맵핑합니다.
본인은 타입과 데이터를 한 ts에 몰아두었기 때문에, 이를 하나하나 맵핑하는 것이 굉장한 노가다일 것.... 그래서 shared에서 관리하다가 차근차근 독립적인 type과 data들이라면 옮겨야겠다!
결론
FSD 구조는 기능(Feature) 중심으로 코드베이스를 모듈화하여 유지보수성과 확장성을 극대화합니다. 이는 프론트엔드 개발의 복잡성을 해결하는 강력한 도구라고 생각하고, 기존의 컴포넌트 중심 접근법에서 기능 중심으로 전환함으로써, 각 기능이 독립적으로 관리되고 확장 가능한 형태로 구조화된다는 점에서 큰 장점이라고 생각합니다. 특히 모든 것을 기능 단위로 묶어서 관리하다 보니, 기능을 추가하거나 수정할 때 변경 범위가 명확해지고, 코드베이스가 커지더라도 쉽게 유지보수 할 수 있다는 점도 매력적입니다.
다만 처음에는 기능 단위로 구조를 잡기엔 시간이 소요될 수 있고, 팀 협업 같은 경우에 FSD 구조 사용에 대해서는 학습과 소통 비용이 있을 수 있을 것 같다는 생각..
FSD의 매력을 느끼고 있는 참에 새로운 프로젝트를 새로운 팀과 시작했을때, 의견차이가 나면 어쩌지..? 라는 쓸데없는 생각