
FSD 폴더 구조 nextjs
이제 충분한 정보를 수집했으므로 FSD 폴더 구조와 Next.js 적용에 대한 종합적인 핸드북을 작성할 수 있습니다.
Feature-Sliced Design (FSD) 폴더 구조 Next.js 핸드북
FSD란 무엇인가?
Feature-Sliced Design(FSD)는 프론트엔드 애플리케이션을 위한 아키텍처 방법론으로, 복잡한 프로젝트를 더 이해하기 쉽고 구조적으로 만들어 끊임없이 변화하는 비즈니스 요구사항에 대응하기 위해 개발되었습니다. FSD는 단순히 코드를 조직화하는 규칙과 관례의 모음이며, 애플리케이션을 기능 단위로 분할하여 각 부분의 역할을 명확하게 정의하는 것을 목표로 합니다.1234
FSD가 만들어진 이유
전통적인 프론트엔드 폴더 구조는 다음과 같은 문제점들을 가지고 있습니다:
- 낮은 응집도(Cohesion): 관련된 코드가 여러 폴더에 분산되어 있음5
- 높은 결합도(Coupling): 순환 참조 발생과 모듈 간 의존성 관리 어려움5
- 규모 확장성 문제: 프로젝트가 커질수록 코드 구조가 복잡해짐6
- 협업 어려움: 명확한 경계가 없어 팀원 간 충돌 발생6
FSD는 이러한 문제들을 해결하기 위해 다음 원칙을 제시합니다:
- 기능 중심 설계: 비즈니스 기능 단위로 코드 조직화7
- 단방향 의존성: 상위 레이어만 하위 레이어에 의존 가능8
- 명시적 공개 인터페이스: Public API를 통한 모듈 간 상호작용9
- 구성 가능성: 작은 기능 단위를 조합하여 큰 기능 구성7
FSD 구조 체계
FSD는 최대 3단계의 계층 구조를 가집니다:
1. 레이어(Layers) - 첫 번째 단계
레이어는 FSD의 최상위 구조로, 각각 고유한 책임 영역을 가집니다:210
app: 애플리케이션 초기화 및 전역 설정
- Provider, Router, 전역 스타일, 전역 타입 선언
- 애플리케이션 전반적으로 적용되는 기능들1
pages: 라우팅 가능한 화면 정의
- 애플리케이션의 각 페이지
- URL과 매핑되는 최상위 컴포넌트들2
widgets: 독립적인 UI 블록
features: 사용자 시나리오와 비즈니스 로직
entities: 비즈니스 엔터티
shared: 재사용 가능한 공통 요소
레이어 간 의존성 규칙
app → pages → widgets → features → entities → shared
- 상위 레이어는 하위 레이어만 import 가능
- 하위 레이어는 상위 레이어를 알 수 없음
- 순환 참조 방지와 안정적인 아키텍처 보장12
2. 슬라이스(Slices) - 두 번째 단계
슬라이스는 비즈니스 도메인별로 코드를 분할합니다:13
- 비즈니스 중심 명명:
user
,post
,comment
,auth
등8 - 독립성 보장: 같은 레이어 내 슬라이스 간 import 금지13
- 높은 응집도: 관련 기능을 한곳에 모음14
3. 세그먼트(Segments) - 세 번째 단계
세그먼트는 기술적 목적에 따라 코드를 그룹화합니다:913
ui: UI 컴포넌트와 관련 요소들
- React 컴포넌트, 스타일, 데이터 포맷팅 함수
model: 비즈니스 로직과 데이터 관리
- 상태 관리, 액션, 셀렉터, 비즈니스 규칙
api: 외부 API와의 통신
- 백엔드 API 메서드, 요청 함수, 데이터 타입
lib: 보조 및 인프라 코드
- 헬퍼 함수, 유틸리티, 인프라 로직
config: 설정과 상수
- 환경 변수, 설정 파일, 상수 정의9
Public API 패턴
각 슬라이스와 세그먼트는 index.ts
파일을 통해 Public API를 정의합니다:159
// features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { RegisterForm } from './ui/RegisterForm';
export { login, logout } from './model/authSlice';
export { selectUser } from './model/authSelectors';
export { fetchUser } from './api/authApi';
Public API의 장점:
Next.js와 FSD 통합
Next.js App Router와의 충돌 문제
Next.js App Router는 app
폴더를 라우팅에 사용하는데, FSD에서도 app
레이어가 존재하여 네이밍 충돌이 발생합니다.17181
해결 방안
├── app/ # Next.js App Router 폴더 (루트)
│ ├── layout.tsx
│ ├── page.tsx
│ └── [route]/page.tsx
├── src/ # FSD 구조
│ ├── app/ # FSD app 레이어
│ ├── pages/ # FSD pages 레이어 (또는 views)
│ ├── widgets/
│ ├── features/
│ ├── entities/
│ └── shared/
├── middleware.ts
└── package.json
Next.js 특화 고려사항
1. 서버 컴포넌트 분리:19
├── shared/
│ ├── server-utils/ # 서버 전용 유틸리티
│ └── utils/ # 클라이언트/서버 공용
- Next.js Pages Router와의 충돌 방지
- 빌드 에러 해결
3. 실제 페이지 컴포넌트 구성:21
// app/dashboard/page.tsx
import { DashboardPage } from '@/src/pages/dashboard';
export default DashboardPage;
실제 구현 예시
기본 FSD 구조
src/
├── app/
│ ├── providers/ # React Query, Redux 등
│ ├── styles/ # 전역 스타일
│ └── types/ # 전역 타입 정의
├── pages/
│ ├── home/
│ │ ├── ui/
│ │ │ └── HomePage.tsx
│ │ └── index.ts
│ └── dashboard/
│ ├── ui/
│ │ └── DashboardPage.tsx
│ └── index.ts
├── widgets/
│ ├── header/
│ │ ├── ui/
│ │ │ └── Header.tsx
│ │ └── index.ts
│ └── sidebar/
│ ├── ui/
│ │ └── Sidebar.tsx
│ └── index.ts
├── features/
│ ├── auth/
│ │ ├── ui/
│ │ │ ├── LoginForm.tsx
│ │ │ └── LogoutButton.tsx
│ │ ├── model/
│ │ │ └── authSlice.ts
│ │ ├── api/
│ │ │ └── authApi.ts
│ │ └── index.ts
│ └── search/
│ ├── ui/
│ │ └── SearchBar.tsx
│ ├── model/
│ │ └── searchModel.ts
│ └── index.ts
├── entities/
│ ├── user/
│ │ ├── model/
│ │ │ └── types.ts
│ │ ├── api/
│ │ │ └── userApi.ts
│ │ └── index.ts
│ └── post/
│ ├── ui/
│ │ └── PostCard.tsx
│ ├── model/
│ │ └── types.ts
│ └── index.ts
└── shared/
├── ui/
│ ├── Button/
│ ├── Input/
│ └── Modal/
├── api/
│ └── client.ts
├── config/
│ └── constants.ts
└── lib/
└── utils.ts
Public API 구현 예시
// entities/user/index.ts
export type { User } from './model/types';
export { fetchUser, updateUser } from './api/userApi';
// features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { useAuth } from './model/authModel';
export { login, logout } from './api/authApi';
// widgets/header/index.ts
export { Header } from './ui/Header';
Import 사용 예시
// pages/home/ui/HomePage.tsx
import { Header } from '@/widgets/header';
import { SearchBar } from '@/features/search';
import { PostCard } from '@/entities/post';
import { Button } from '@/shared/ui/Button';
export const HomePage = () => {
return (
<div>
<Header />
<SearchBar />
<PostCard />
<Button>Click me</Button>
</div>
);
};
의존성 관리와 품질 보증
ESLint 플러그인 활용
eslint-plugin-boundaries 설정:2223
{
"plugins": ["boundaries"],
"settings": {
"boundaries/elements": [
{ "type": "shared", "pattern": "src/shared/*" },
{ "type": "entities", "pattern": "src/entities/*" },
{ "type": "features", "pattern": "src/features/*" },
{ "type": "widgets", "pattern": "src/widgets/*" },
{ "type": "pages", "pattern": "src/pages/*" },
{ "type": "app", "pattern": "src/app/*" }
]
},
"rules": {
"boundaries/element-types": ["error", {
"default": "disallow",
"rules": [
{ "from": "shared", "allow": [] },
{ "from": "entities", "allow": ["shared"] },
{ "from": "features", "allow": ["shared", "entities"] },
{ "from": "widgets", "allow": ["shared", "entities", "features"] },
{ "from": "pages", "allow": ["shared", "entities", "features", "widgets"] },
{ "from": "app", "allow": ["shared", "entities", "features", "widgets", "pages"] }
]
}]
}
}
Import 순서 정리:23
{
"rules": {
"import/order": ["error", {
"groups": ["builtin", "external", "internal", ["parent", "sibling"], "index", "object", "type"],
"pathGroups": [
{ "pattern": "@/app/**", "group": "internal", "position": "before" },
{ "pattern": "@/pages/**", "group": "internal", "position": "before" },
{ "pattern": "@/widgets/**", "group": "internal", "position": "before" },
{ "pattern": "@/features/**", "group": "internal", "position": "before" },
{ "pattern": "@/entities/**", "group": "internal", "position": "before" },
{ "pattern": "@/shared/**", "group": "internal", "position": "before" }
]
}]
}
}
FSD의 장점과 단점
장점
- 프로젝트가 커져도 구조 유지
- 기능별 독립적 개발 가능
- 팀 협업 효율성 증대
- 명확한 책임 분리
- 순환 참조 방지
- 리팩토링 용이성
- 표준화된 구조
- 새로운 개발자 적응 용이
- 코드 탐색성 향상
단점과 한계
- 초기 개념 이해 어려움
- 레이어/슬라이스 구분의 모호함
- 팀 적응 시간 필요
복잡성 증가:24
- 작은 프로젝트에는 과도할 수 있음
- 많은 파일과 폴더 구조
- 초기 설정 비용
- 엄격한 규칙 준수의 어려움
- 실제 비즈니스 로직의 복잡성
- 기존 프로젝트 적용 어려움
실무 적용 가이드
점진적 도입 전략
1단계: 기본 레이어 구성:24
src/
├── app/
├── pages/
├── widgets/
└── shared/
2단계: 필요시 확장:
src/
├── app/
├── pages/
├── widgets/
├── features/ # 비즈니스 로직 복잡해지면 추가
├── entities/ # 도메인 모델 필요시 추가
└── shared/
팀 합의사항 정의
슬라이스 네이밍 규칙:10
- 비즈니스 도메인 기반 명명
- 동사형(features) vs 명사형(entities) 구분
- 팀 내 용어 통일
Public API 정책:9
- 필수 export 요소 정의
- Internal 모듈 접근 금지 규칙
- 린팅 규칙 적용
성능 고려사항
배럴 파일 최적화:28
- Tree-shaking 고려한 export 구성
- 번들 크기 모니터링
- 필요시 직접 import 허용
결론
FSD는 대규모 프론트엔드 프로젝트의 구조적 복잡성을 해결하기 위한 강력한 방법론입니다. Next.js와 함께 사용할 때는 네이밍 충돌 문제를 해결하고, 서버/클라이언트 컴포넌트 분리를 고려해야 합니다.
성공적인 FSD 도입을 위해서는:
- 팀의 프로젝트 규모와 복잡도 평가
- 점진적 도입과 팀 교육
- ESLint 등 자동화 도구 활용
- 실용적 접근과 규칙의 유연한 적용
이 핸드북을 통해 FSD의 핵심 개념을 이해하고, Next.js 프로젝트에 효과적으로 적용할 수 있기를 바랍니다.
Footnotes
-
https://blog.bizspring.co.kr/테크/효율적인-프론트엔드-설계를-위한-feature-sliced-designfsd의-이해/ ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
https://velog.io/@clydehan/FSDFeature-Sliced-Design-완벽-가이드 ↩
-
https://www.codecentric.de/en/knowledge-hub/blog/feature-sliced-design-and-good-frontend-architecture ↩ ↩2 ↩3
-
https://velog.io/@seoyong-lee/FSDFeature-Sliced-Design-Architecture ↩ ↩2 ↩3
-
https://github.com/vgratsilev/eslint-plugin-fsd-import/blob/main/docs/rules/layer-imports.md ↩ ↩2
-
https://sungali.tistory.com/entry/FSD-Reference-Slices-and-segments ↩ ↩2 ↩3
-
https://github.com/feature-sliced/steiger/blob/master/packages/steiger-plugin-fsd/src/public-api/README.md ↩ ↩2
-
https://velog.io/@xxyeon129/기능-분할-설계Feature-Sliced-Design-FSD-아키텍처 ↩ ↩2 ↩3
-
https://hackernoon.com/how-to-deal-with-the-nextjs-app-router-and-fsd-problem ↩ ↩2
-
https://dev.to/m_midas/how-to-deal-with-nextjs-using-feature-sliced-design-4c67 ↩ ↩2
-
https://23life.tistory.com/entry/nextjs에-FSD-폴더-구조-패턴-적용하기 ↩ ↩2
-
https://kr.linkedin.com/posts/boaz-hwang_nextjs%EC%97%90%EC%84%9C-feature-sliced-designfsd%EC%9D%84-%EC%A0%81%EC%9A%A9%ED%95%A0-activity-7204100009250684928-47Gj ↩
-
https://www.linkedin.com/pulse/enforcing-architectural-boundaries-eslint-amr-bahaa-4vljf ↩
-
https://www.godeltech.com/blog/feature-sliced-design-a-guide-to-scalable-frontend-architecture/ ↩ ↩2 ↩3 ↩4 ↩5
-
https://hackernoon.com/understanding-feature-sliced-design-benefits-and-real-code-examples ↩
-
https://dev.to/algoorgoal/feature-sliced-design-review-22k0 ↩
-
https://www.linkedin.com/posts/54k_why-feature-sliced-design-fsd-isnt-a-revolutionary-activity-7288485090102272000-IZD0 ↩