ReactFSD(기능 분활 설계)에 대해 알아보자

최근 프로젝트를 시작하면서 파일 구조에서 대해서 고민되기 시작했다. NextJS App Router를 사용중이고, 평소에 사용하던 폴더 구조는 아래와 같다.

root
└── app/
    └── login/
        ├── _ui // LoginForm, LoginButton (공통 컴포넌트들의 조합)
        └── page.tsx 

위와 같은 구조는 기능이 간단한 경우에는 상관없었지만, 기능이 커지면 커질수록 구조를 파악하기 힘들었다.

스토리 핀 이미지

더 이상은 안된다는 마음에 Github에 올라와있는 여러 프로젝트를 찾아봤는데 레포마다 다르지만, 대부분 features/[domain]/...으로 정리하고 있었다.


궁금해서 더 찾아보니 모두들 FSD(기능 분할 설계)를 사용한 것이였다...!🫢

FSD란?

Feature-Sliced Design(FSD)는 프론트엔드 애플리케이션을 위한 아키텍처적 설계 방법론이다.
간단히 말해, 코드를 어떻게 조직할지에 대한 규칙과 관례들의 모음이다.
이 방법론의 주요 목적은 끊임없이 변하는 비즈니스 요구사항 속에서도 프로젝트를 더 이해하기 쉽고 체계적으로 유지하는 것이다.


위는 FSD 사이트에 나와있는 공식 설명이다. 기능을 중심으로 코드를 구조화하는 방식으로 이해하면 쉬울것 같다.


FSD는 크게 3가지 개념으로 구성된다.


레이어

레이어는 현재 7가지로 표준화 되어있다. 모두 사용할 필요는 없지만, FSD에서 각 레이어는 이름에 따라 명확한 책임과 역할을 가진다.

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레이어에서 사용된다. 세그먼트의 이름은 표준화되지는 않았지만 대부분 아래와 같이 사용한다.



공개 API 규칙

슬라이스의 내부는 사실 어떤 방식으로 구성되어 있어도 괜찮다. 중요한 점은 외부에서 접근할때는 반드시 Public API를 통해서만 접근해야한다. 여기서 Public API란 각 슬라이스의 index.js혹은 index.ts를 통해 이루어진다.

이렇게 하면 슬라이스 내부 구조가 바뀌더라도 외부에는 영향을 주지 않으므로 안정적인 캡슐화가 가능하다.

// ✅ public API를 통해 접근
import { usePost } from '@/features/post';
 
// ❌ 내부 구조에 직접 접근할 수 없음!
import { usePost } from '@/features/post/hooks/usePost';

장점

  1. 일관성: 구조가 표준화 되어 있기때문에 프로젝트 전체가 일관성을 가지고, 새로운 팀원이 합류 했을때 금방 이해하기 쉬움
  2. 변경과 리팩토링에도 유연하게 대처할 수 있음
  3. 재사용성의 용이: 레이어에 따라 코드를 재사용 가능하게 만들거나 로컬하게 만들 수 있어 DRY 원칙(Don't Repeat Yourself)과 실용성 사이에서 균형을 맞추 수 있다.
  4. 비즈니스와 사용자 중심의 코드 설계

단점

  1. 높은 진입 장벽
  2. 초기 구조 세팅이 번거로울 수 있음
  3. 규칙으로 인해 오히려 복잡해질 수 있음
  4. 작은팀/프로젝트에서는 이점이 크지 않음

그래서...?

나의 경우에는 프로젝트 기능이 많아지고 폴더 구조가 점점 더 복잡해진다고 느꼈던터라 확실히 기능을 중심으로 코드가 분리되니 확실이 깔끔해져서 좋았다.


FSD 대부분 예시가 React기반이고 이를 NextJS에 맞춰서 적용하다보니, 조금 애매한 포인트들이 있는 것 같긴하다.


하지만 어떤 아키텍처든지 무조건적으로 정답은 아니고 팀과 프로젝트를 상황에 따라 유연하게 적용하는게 좋은 방향이라고 생각한다.


더 자세한 예시가 필요하다면 아래 FSD 공식 사이트에서 나와있는 예시 사이트를 참고해도 좋을 것 같다.

+ 여담: 🤖 Claude Code 후기

추가로 이번에 FSD를 도입할때 Claude Code를 사용했는데 꽤나 만족스러웠다. 기존에 이미 코드가 많다보니 하나하나 다시 분리하는게 막막했는데 파일 구조 변경부터 빌드 테스트까지 알아서 척척해주고 몇시간도 걸리지도 않았다..정말 GOAT👍🏻🥹


📃 참고 문서

FSD 문서 기능 분활 설계 - 번역글