이번에 새롭게 Next.js 프로젝트를 만들면서 좀 애매한 부분은
프로젝트 구조를 어떻게 가져갈까? 였다.
React App을 만들 때는 Atomic Design을 채택해서 재미 좀 쏠쏠하게 봤었는데
Next로 넘어가니 app 하위에 필수 파일을 위치 시켜야 해서 내 마음대로 할 수 없었다.
마침 최근에 Feature-Sliced Design을 아주 살짝 맛보기로 JWT 로그인을 구현했던 경험이 있어
이참에 재대로 써먹어 보자고 생각했고 내용도 정리해 보기로 했다!
Feature-Sliced Design
Feature-Sliced Design은 소프트웨어 시스템을 기능 중심으로 나누어 모듈화 하고, 각 기능을 독립적으로 관리하는 아키텍처이다.
Nextjs가 등장하면서 기존에 많이 쓰던 Atomic Design처럼 컴포넌트를 작은 단위로 쪼개기만 하는 것에는 한계가 있으며 Next.js의 기존 app하위에 필수 파일을 위치 시켜야 하는 구조 때문에 요즘 FSD가 떠오른다고 한다.
FSD의 기본 원칙은 다음과 같다.
여러 검색 결과 중 가장 명확하게 분할을 하신 분이 있긴 하지만
설명이 너무 어려워서 초등학생 수준으로 정리해보았다..
- 🧩 기능 기반 분할 (Feature-Sliced)
- 폴더 구조를 화면(UI) 중심이 아니라 "기능 단위"로 나눈다.
- 한 가지 기능을 한 곳에서 개발/수정/테스트할 수 있도록 하기
- 각 기능은 외부에 의존하지 않고 독립적으로 동작
- 🏛 레이어링 (Layered Design)
- 단방향으로 참조를 하며 하위 계층이 상위 계층을 참조할 수 없음.
- shared < entities < features < widgets < processes < pages
- 📁 폴더 구조 (File Structure by Responsibility)
- 어디에 어떤 파일이 있어야 하는지 모두가 예측 가능하게 만들기
- 하나의 폴더(모듈)는 하나의 목적만 가진다
- 폴더를 역할, 책임, 기능에 따라 체계적으로 나눈다.
- ♻️ 재사용성 (Reusability)
- 중복되는 UI, 로직, 기능은 재사용할 수 있도록 설계
- 공통 컴포넌트, 유틸 함수, 타입 정의는
shared/
에 모은다.
FSD의 주요 디렉토리 이름 의미
최하위 부터 차근차근 설명해보면 다음과 같다.
- shared : 모든 곳에서 재사용 가능한 “도구 단위”
- shared/ui : 공통 디자인 요소 (버튼, 모달 등)
- shared/lib : 유틸 함수나 커스텀 훅 등
- shared/types : 전역 타입 정의
- shared/config : 환경 설정, 메타정보 등
- entities : 작고 독립적인 “도메인 객체” 단위
- 단순한 UI형태의 State만 사용 가능
- api호출, 전역 상태 관리, 사용자와 상호작용에 반응하는 로직
- 도메인 내부에 한정되어야 한다. 말이 너무 어려워서 검색 결과 첨부.
- entities는 아래의 두 가지 디렉토리를 가질 수 있다.
- entities/model : 도메인 타입과 데이터 가공 유틸 함수
- entities/ui : 데이터를 보여줄 UI 컴포넌트 모음

- features : 사용자 상호작용 또는 하나의 기능의 단위
- “하나의 행동(Action)” 또는 "하나의 목적"에 집중 즉, 이거 하나만으로 어떤 동작을 완성할 수있어야 함.
- UI 컴포넌트를 활용하지만, 외부 API 호출이나 상태 처리도 포함
- 4가지 디렉토리 구조를 채택할 수 있다.
- features/ui : 해당 기능 만을 위한 UI 컴포넌트
- features/model : Hook, State, Store, API 호출 등 상태/로직 처리
- features/lib : 비지니스 유틸 함수
- features/api : API 요청 코드
- widgets : 여러개의 기능과, 엔티티를 조합하여 화면의 구역을 구성하는 단위.
- 직접 API 호출은 잘 하지 않고, 대부분 상위에서 받은 데이터를 받아서 조립.
- 동작보단 시각적 구성에 집중 해야 한다.
- pages : 전체를 조립하여 “화면을 보여주는 단위”
- 위젯들을 조합하여 최종적으로 보여질 컨탠츠를 조합
- app : 최상위 계층으로 화면+전반적인 기능을 합친 단위
- 해더와 푸터 같은 레이어에 해당하는 부분이 여기서 조립됨.
- 그 외 전역 상태관리 (Redux Store, Provider 등을 여기서 관리)
- Web App의 메타 등을 작업
이제 실제로 Next 프로젝트를 설계해 보자.
여러 블로그들을 돌아다니다 맘에 드는 프로젝트 구조를 확인 했다.
- Next 프로젝트의 최상위에는 /app 와 /src 디렉토리를 위치시킨다.
- /app 하위에는 서버에서 동작할 /api와 라우팅을 위한 page를 위치 시킨다.
- 단 page는 URL에서 참조가 되지 않게 (page) 로 괄호를 사용한다.
- src 하위에는 나머지 4개의 레이어를 위치 시킨다.
그리하여 최종 탄생한 나의 초기 디렉토리 구조는 다음과 같다.
├── app/ │ ├── (page)/page.tsx │ └── api/ ├── src/ │ ├── shared/ │ │ ├──client/ │ │ ├──server/ │ │ └──common/ │ ├── entities/ │ │ ├──notionReder/ │ │ └──postCard/ │ ├── features/ │ │ ├──server/ │ │ └──client/ │ └── widgets/ │ ├──header/ │ ├──footer/ │ │ └──sideNavigator/ ├── public/ └── ...etc configs
2025.6.8
구조를 따져보니 개편할 필요가 있어서 개편했다.
기존에는 각 레이어에 모두 서버와 클라이언트를 구분해서 임포트 태그를 보고 이게 SSR/CSR중 뭔지 구분을 쉽게 하려고 했었다.
api를 호출하다보니 TanstackQuery를 적용해야하는 CSR컴포넌트를 구성하다보니 아래와 같은 깨달음을 얻었다.
- entities는 데이터를 받아와서 보여주는 컴포넌트 임으로 SSR/CSR양쪽에서 쓸수 있는 컴포넌트다.
- 실질적으로 해당 도메인의 데이터를 받아오는 함수외에 가공처리 또한 SSR/CSR양쪽에서 가져올 수있다.
그 결과
- entities/widgets에는 서버와 클라이언트를 나누지 않아도 된다
- features에는 서버와 클라이언트를 나눠도 된다.
- 유틸리티 함수와 타입은 shared에서 보관해야 어느 레이어든 가져올 수 있다.
- 노션 api인스턴스와 같은 SSR에서만 쓰는 게 확실하고 provider처럼 CSR에서만 쓰는게 확실한건 분리하자