최근 프로젝트를 시작하면서 파일 구조에서 대해서 고민되기 시작했다. NextJS App Router를 사용중이고, 평소에 사용하던 폴더 구조는 아래와 같다.
root
└── app/
└── login/
├── _ui // LoginForm, LoginButton (공통 컴포넌트들의 조합)
└── page.tsx 위와 같은 구조는 기능이 간단한 경우에는 상관없었지만, 기능이 커지면 커질수록 구조를 파악하기 힘들었다.

더 이상은 안된다는 마음에 Github에 올라와있는 여러 프로젝트를 찾아봤는데
레포마다 다르지만, 대부분 features/[domain]/...으로 정리하고 있었다.
궁금해서 더 찾아보니 모두들 FSD(기능 분할 설계)를 사용한 것이였다...!🫢
FSD란?
Feature-Sliced Design(FSD)는 프론트엔드 애플리케이션을 위한 아키텍처적 설계 방법론이다.
간단히 말해, 코드를 어떻게 조직할지에 대한 규칙과 관례들의 모음이다.
이 방법론의 주요 목적은 끊임없이 변하는 비즈니스 요구사항 속에서도 프로젝트를 더 이해하기 쉽고 체계적으로 유지하는 것이다.
위는 FSD 사이트에 나와있는 공식 설명이다. 기능을 중심으로 코드를 구조화하는 방식으로 이해하면 쉬울것 같다.
FSD는 크게 3가지 개념으로 구성된다.
- 레이어(Layers)
- 슬라이스(Slices)
- 세그먼트(Segments)
레이어
레이어는 현재 7가지로 표준화 되어있다. 모두 사용할 필요는 없지만, FSD에서 각 레이어는 이름에 따라 명확한 책임과 역할을 가진다.
App: 앱이 실행되는데 필요한 모든것(e.g. 라우팅, 엔드포인트, 전역 스타일, Provider)Processes: 여러 페이지에 걸쳐있는 프로세스 기능(더 이상 사용되지 않음)Pages: 라우팅에서 사용되는 모든 페이지Widgets: 완전한 하나의 독립적인 기능이나 UI로 보통 하나의 완전한 기능을 담당Features: 비즈니스 로직을 중심으로 구현된 재사용이 가능한 기능(e.g. 로그인, 결제)Entities: 사용자, 상품 같은 비즈니스 엔티티Shared: 비즈니스 로직과 분리된 재사용 가능한 기능들(반드시 비즈니스 로직과 분리되어야하는 것은 아님)
src/
├── app/ # router, providers
├── pages/ # HomePage, ProductPage
├── widgets/ # Header, Sidebar
├── features/ # login, add-to-cart
├── entities/ # user, product
└── shared/ # Button, Input슬라이스
슬라이스는 비즈니스 도메인을 기준으로 코드가 분리된다. 이를 통해 관련된 모듈을 가까이 두어 코드를 더 쉽게 탐색할 수 있게 한다. 주의할 점은 같은 레이어의 슬라이스들은 서로 독립적이어야 하며, 이는 코드의 응집도를 높이고 결합도를 낮추는 데 기여한다.
말로는 조금 어려우니 아래 예시를 보자.
// features/product-rating/ui/RatingForm.tsx
import { AddToCartButton } from '../../add-to-cart/ui/AddToCartButton' // ❌
function RatingForm() {
return (
<form>
<StarRating />
<AddToCartButton /> {/* 평점 기능이 장바구니 기능에 의존 */}
</form>
)
}// widgets/product-card/ui/ProductCard.tsx (상위 레이어에서 조합)
import { ProductRating } from '../../features/product-rating'
import { AddToCartButton } from '../../features/add-to-cart'
function ProductCard() {
return (
<div>
<ProductInfo />
<ProductRating />
<AddToCartButton /> {/* 위젯에서 기능들을 조합 */}
</div>
)
}결론은 같은 레이어의 기능들을 조합하기 위해서는 상위 레이어에서 조합해야한다는 것이다.
세그먼트
용도별로 구분된 세그먼트는 슬라이스와 App레이어, Shared레이어에서 사용된다.
세그먼트의 이름은 표준화되지는 않았지만 대부분 아래와 같이 사용한다.
ui: 화면에 표시되는 것과 관련된 모든것(e.g. UI 컴포넌트, Date Formatter, stlyles..)api: 백엔드 연결과 관련된 것들(e.g. fetch, mappers 등등)model: 데이터 모델(e.g. schemas, interfaces..)lib: 슬라이스내에서 사용되는 라이브러리 코드config: 슬라이스내에서 필요한 설정값
공개 API 규칙
슬라이스의 내부는 사실 어떤 방식으로 구성되어 있어도 괜찮다. 중요한 점은 외부에서 접근할때는 반드시 Public API를 통해서만 접근해야한다. 여기서 Public API란 각 슬라이스의 index.js혹은 index.ts를 통해 이루어진다.
- 각 슬라이스는 반드시 Public API를 정의 파일을 가진다.
- Slice 외부의 모듈은 내부 디렉토리 구조에 직접 접근하지 않고, 오직 Public API를 통해서만 코드를 가져와야 한다.
이렇게 하면 슬라이스 내부 구조가 바뀌더라도 외부에는 영향을 주지 않으므로 안정적인 캡슐화가 가능하다.
// ✅ public API를 통해 접근
import { usePost } from '@/features/post';
// ❌ 내부 구조에 직접 접근할 수 없음!
import { usePost } from '@/features/post/hooks/usePost';장점
- 일관성: 구조가 표준화 되어 있기때문에 프로젝트 전체가 일관성을 가지고, 새로운 팀원이 합류 했을때 금방 이해하기 쉬움
- 변경과 리팩토링에도 유연하게 대처할 수 있음
- 재사용성의 용이: 레이어에 따라 코드를 재사용 가능하게 만들거나 로컬하게 만들 수 있어
DRY 원칙(Don't Repeat Yourself)과 실용성 사이에서 균형을 맞추 수 있다. - 비즈니스와 사용자 중심의 코드 설계
단점
- 높은 진입 장벽
- 초기 구조 세팅이 번거로울 수 있음
- 규칙으로 인해 오히려 복잡해질 수 있음
- 작은팀/프로젝트에서는 이점이 크지 않음
그래서...?
나의 경우에는 프로젝트 기능이 많아지고 폴더 구조가 점점 더 복잡해진다고 느꼈던터라 확실히 기능을 중심으로 코드가 분리되니 확실이 깔끔해져서 좋았다.
FSD 대부분 예시가 React기반이고 이를 NextJS에 맞춰서 적용하다보니, 조금 애매한 포인트들이 있는 것 같긴하다.
하지만 어떤 아키텍처든지 무조건적으로 정답은 아니고 팀과 프로젝트를 상황에 따라 유연하게 적용하는게 좋은 방향이라고 생각한다.
더 자세한 예시가 필요하다면 아래 FSD 공식 사이트에서 나와있는 예시 사이트를 참고해도 좋을 것 같다.
+ 여담: 🤖 Claude Code 후기
추가로 이번에 FSD를 도입할때 Claude Code를 사용했는데 꽤나 만족스러웠다.
기존에 이미 코드가 많다보니 하나하나 다시 분리하는게 막막했는데
파일 구조 변경부터 빌드 테스트까지 알아서 척척해주고 몇시간도 걸리지도 않았다..정말 GOAT👍🏻🥹