
그린플래너 웹 애플리케이션 FSD 리팩토링 가이드
현재 프로젝트 구조를 분석한 결과, 다음과 같은 단계별 FSD 리팩토링 전략을 제안합니다.
현재 구조 분석
현재 그린플래너 프로젝트는 기술 중심(Tech-centric) 구조로 되어 있습니다:12
- 컴포넌트별 분류:
components/
,hooks/
,store/
,utils/
- Next.js App Router와 혼재된 구조
- 도메인 로직이 여러 폴더에 분산
FSD 리팩토링 전략
1단계: 기본 FSD 구조 설정
src/
├── app/ # FSD app 레이어
│ ├── providers/ # AppProviders, AuthTokenProvider 등
│ ├── styles/ # globals.css, Tailwind 설정
│ ├── config/ # 전역 설정
│ └── index.ts
├── pages/ # FSD pages 레이어 (또는 views)
├── widgets/ # 독립적 UI 블록
├── features/ # 비즈니스 기능
├── entities/ # 비즈니스 엔터티
└── shared/ # 공통 요소
app/ # Next.js App Router (루트 유지)
├── (withRightPanel)/
├── api/
├── layout.tsx
└── page.tsx
2단계: 도메인별 기능 분석 및 분류
Entities (비즈니스 엔터티):
user
- 사용자 정보building
- 건물 정보report
- 보고서ticket
- 이용권land
- 토지 정보
Features (사용자 기능):
auth
- 인증 (로그인, 회원가입)search-building
- 건물 검색generate-report
- 보고서 생성manage-wishlist
- 관심 부동산 관리energy-analysis
- 에너지 분석
Widgets (UI 블록):
header
- 네비게이션map-viewer
- 지도 컴포넌트property-panel
- 부동산 정보 패널report-viewer
- 보고서 뷰어
3단계: 점진적 마이그레이션 계획
src/shared/
├── ui/ # 기존 components/ui/* → shared/ui/*
│ ├── button/
│ ├── modal/
│ └── form/
├── api/ # API 클라이언트 통합
│ ├── client.ts
│ ├── types.ts
│ └── interceptors.ts
├── config/ # 기존 constants/* → shared/config/*
│ ├── constants.ts
│ ├── env.ts
│ └── auth.config.ts
├── lib/ # 기존 utils/* → shared/lib/*
│ ├── utils.ts
│ ├── date.ts
│ └── validation.ts
└── types/ # 전역 타입 정의
├── api.ts
└── common.ts
Phase 2: Entities 레이어 구축:
src/entities/
├── user/
│ ├── model/
│ │ ├── types.ts # User 인터페이스
│ │ └── store.ts # 기존 store/auth/* 일부
│ ├── api/
│ │ └── userApi.ts # 사용자 관련 API
│ └── index.ts
├── building/
│ ├── model/
│ │ ├── types.ts # Building 관련 타입
│ │ └── store.ts # 기존 store/map/* 일부
│ ├── api/
│ │ └── buildingApi.ts
│ └── index.ts
└── report/
├── model/
│ ├── types.ts
│ └── reportStore.ts
├── api/
│ └── reportApi.ts
└── index.ts
Phase 3: Features 레이어 구축:
src/features/
├── auth/
│ ├── ui/
│ │ ├── LoginForm.tsx # 기존 components/auth/*
│ │ ├── RegisterForm.tsx
│ │ └── SocialLogin.tsx
│ ├── model/
│ │ ├── authSlice.ts # 기존 store/auth/*
│ │ └── hooks.ts # 기존 hooks/auth/*
│ ├── api/
│ │ └── authApi.ts
│ └── index.ts
├── search-building/
│ ├── ui/
│ │ ├── SearchBar.tsx
│ │ └── SearchResults.tsx
│ ├── model/
│ │ └── searchStore.ts
│ └── index.ts
└── manage-wishlist/
├── ui/
│ ├── WishButton.tsx
│ └── WishList.tsx
├── model/
│ └── wishStore.ts # 기존 store/wishBld/*
└── index.ts
Phase 4: Widgets 레이어 구축:
src/widgets/
├── header/
│ ├── ui/
│ │ └── Header.tsx # 기존 components/common/Header
│ ├── model/
│ │ └── navigationStore.ts
│ └── index.ts
├── map-viewer/
│ ├── ui/
│ │ ├── MapContainer.tsx # 기존 components/map/*
│ │ └── MapControls.tsx
│ ├── model/
│ │ └── mapStore.ts # 기존 store/map/*
│ └── index.ts
└── property-panel/
├── ui/
│ └── PropertyPanel.tsx # 기존 components/ui/right-panels/*
├── model/
│ └── panelStore.ts # 기존 store/ui/*
└── index.ts
Phase 5: Pages 레이어 구축:
src/pages/ # 또는 views로 네이밍
├── home/
│ ├── ui/
│ │ └── HomePage.tsx
│ └── index.ts
├── map/
│ ├── ui/
│ │ └── MapPage.tsx
│ ├── model/
│ │ └── mapPageStore.ts
│ └── index.ts
└── terms/
├── ui/
│ ├── PrivacyPolicy.tsx
│ └── ServiceTerms.tsx
└── index.ts
4단계: Public API 패턴 적용
// src/features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { RegisterForm } from './ui/RegisterForm';
export { useAuth } from './model/hooks';
export { login, logout } from './api/authApi';
export type { LoginCredentials, RegisterData } from './model/types';
// src/entities/user/index.ts
export type { User, UserProfile } from './model/types';
export { useUserStore } from './model/store';
export { fetchUser, updateUser } from './api/userApi';
// src/widgets/header/index.ts
export { Header } from './ui/Header';
5단계: 의존성 규칙 적용
// .eslintrc.js
{
"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"] }
]
}]
}
}
6단계: Next.js 특화 처리
// app/map/page.tsx
import { MapPage } from '@/src/pages/map';
export default MapPage;
// app/(withRightPanel)/layout.tsx
import { Header } from '@/src/widgets/header';
import { RightPanelContainer } from '@/src/widgets/panel-container';
export default function WithRightPanelLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<>
<Header />
<div className="flex">
<main>{children}</main>
<RightPanelContainer />
</div>
</>
);
}
마이그레이션 우선순위
높은 우선순위 (즉시 시작)
- Shared 레이어 구축 - 기존
shared/ui
,lib
,config
이동 - Public API 패턴 도입 - 각 모듈별
index.ts
생성 - Import 경로 정리 - 절대 경로 사용 (
@/src/shared/ui/Button
)
중간 우선순위 (2주 내)
- Entities 분리 -
user
,building
,report
엔터티 생성 - Features 분리 -
auth
,search
기능 우선 분리 - ESLint 규칙 적용 - 의존성 규칙 강제
낮은 우선순위 (1개월 내)
- Widgets 분리 - 복잡한 UI 컴포넌트들 분리
- Pages 리팩토링 - 페이지별 로직 정리
- 테스트 코드 추가 - 각 레이어별 테스트 작성
예상 효과
즉시 개선 효과
장기적 개선 효과
주의사항
점진적 적용 필요
실용적 접근
- 작은 컴포넌트는 무리하게 분리하지 않음16
- 비즈니스 로직이 명확하지 않은 경우 기존 구조 유지
- 프로젝트 규모에 맞는 적절한 수준의 FSD 적용
이러한 단계별 접근을 통해 그린플래너 프로젝트를 점진적으로 FSD 구조로 전환할 수 있으며, 코드의 유지보수성과 확장성을 크게 개선할 수 있을 것입니다.
Footnotes
-
https://blog.bizspring.co.kr/테크/효율적인-프론트엔드-설계를-위한-feature-sliced-designfsd의-이해/ ↩ ↩2 ↩3
-
https://velog.io/@ujinsimss_/FSD-%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80-FSD-%ED%8C%A8%ED%84%B4%EC%9D%84-Next-App-%EB%9D%BC%EC%9A%B0%ED%8C%85%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90 ↩
-
https://hackernoon.com/how-to-deal-with-the-nextjs-app-router-and-fsd-problem ↩ ↩2
-
https://www.codecentric.de/en/knowledge-hub/blog/feature-sliced-design-and-good-frontend-architecture ↩ ↩2 ↩3 ↩4
-
https://blog.bitsrc.io/developing-scalable-frontends-with-feature-sliced-design-fsd-a2e5aa33d02c ↩
-
https://github.com/feature-sliced/steiger/blob/master/packages/steiger-plugin-fsd/src/public-api/README.md ↩
-
https://github.com/vgratsilev/eslint-plugin-fsd-import/blob/main/docs/rules/layer-imports.md ↩
-
https://dev.to/m_midas/how-to-deal-with-nextjs-using-feature-sliced-design-4c67 ↩
-
https://www.godeltech.com/blog/feature-sliced-design-a-guide-to-scalable-frontend-architecture/ ↩ ↩2
-
https://hackernoon.com/understanding-feature-sliced-design-benefits-and-real-code-examples ↩
-
https://dev.to/theonlineaid/mastering-nextjs-structuring-your-large-scale-project-for-success-42bo ↩
-
https://dev.to/algoorgoal/feature-sliced-design-review-22k0 ↩