역할 기반의 폴더 구조에서 Domain-First 기반의 폴더 구조로 전환하기

섭승이·2025년 8월 6일

Next.js로 Edukit 프로젝트를 개발하면서 겪었던 다양한 문제들과 해결 과정을 공유하려고 합니다.

두 번째 이야기로 역할 기반의 폴더 구조에서 Domain 기반의 폴더구조 왜 바꿨는지, 어떻게 바꿨는지, 그에 따른 개선 효과가 무엇이었는지에 대해 작성하겠습니다.


📂 과거 폴더 구조

src/
├── app/                            App Router (페이지, 레이아웃)
│   ├── (auth)/
│   │   ├── find-password/page.tsx
│   │   ├── ...
│   │   └── layout.tsx
│   ├── (record)/
│   │   ├── manage-behavior/page.tsx
│   │   ├── ...
│   │   ├── page.tsx
│   │   └── layout.tsx
│   ├── globals.css
│   ├── layout.tsx
│   └── metadata.ts
│
├── components/                     모든 컴포넌트 (혼재)
│   ├── analytics/                    # 분석 도구
│   ├── editor/                       # 에디터
│   ├── error/                        # 에러 (공통 UI)
│   ├── find-password/                # 비밀번호 찾기 (auth 도메인)
│   ├── header/                       # 헤더 (공통 Layout)
│   ├── input/                        # 입력 필드 (공통 UI)
│   ├── loading/                      # 로딩 (공통 UI)
│   ├── login/                        # 로그인 (auth 도메인)
│   ├── modal/                        # 모달 (공통 UI)
│   ├── mypage/                       # 마이페이지 (profile 도메인)
│   ├── notice/                       # 공지사항 (notice 도메인)
│   ├── pagination/                   # 페이지네이션 (공통 UI)
│   ├── record/                       # 생활기록부 (record 도메인)
│   ├── sidebar/                      # 사이드바 (공통 Layout)
│   ├── signup/                       # 회원가입 (auth 도메인)
│   ├── student-record-write/         # 생활기록부 작성 (record 도메인)
│   ├── textarea/                     # 텍스트 영역 (공통 UI)
│   └── verify-email/                 # 이메일 인증 (auth 도메인)
│
├── hooks/                          훅
│   ├── api/                          # API 관련 훅 (Tanstack-Query로 래핑)
│   │   ├── auth/                     # 인증 API 훅
│   │   ├── notice/                   # 공지사항 API 훅
│   │   ├── profile/                  # 프로필 API 훅
│   │   ├── student-manage/           # 학생 관리 API 훅
│   │   └── write-record/             # 생활기록부 작성 API 훅
│   ├── use-auto-resize-textarea.tsx  # 공통 커스텀 훅
│   └── use-mobile.tsx                # 공통 커스텀 훅
│
├── services/                       Fetch 로직 (도메인별 분리)
│   ├── auth/
│   ├── notice/
│   ├── profile/
│   ├── student-manage/
│   └── write-record/
│
├── types/                          타입 정의 (도메인별 분리)
│   ├── auth/
│   ├── notice/
│   ├── profile/
│   ├── record/
│   └── shared/
│
├── contexts/                       컨텍스트
│   └── auth/
│
├── constants/                      상수
│   ├── record-data.ts
│   ├── signup-data.ts
│   ├── student-data.ts
│   └── user-info-data.ts
│
├── lib/                            설정 파일
│   ├── actions/
│   ├── amplitude/
│   ├── api.ts
│   ├── errors.ts
│   ├── msw-provider.tsx
│   ├── tanstack-query-provider.tsx
│   └── utils.ts
│
├── util/                           유틸 함수
│   ├── calculate-byte.ts
│   ├── download-excel.ts
│   ├── formatDate.ts
│   └── get-korea-formatted-time-stamp.ts
│
├── configs/                        설정
│   └── sidebar-config.ts
│
├── mocks/                          MSW 모킹
│   ├── handlers/
│   ├── browser.ts
│   ├── index.ts
│   └── utils/
│
└── mock-server/                    Mock 서버
    └── server.ts


❌ 주요 문제점

1. components/ 폴더의 복잡성 증가

현재 상황:
components/
├── header/           (공통 레이아웃)
├── input/            (공통 UI)
├── modal/            (공통 UI)
├── login/            (도메인별 - auth)
├── signup/           (도메인별 - auth)
├── mypage/           (도메인별 - profile)
├── notice/           (도메인별 - notice)
├── record/           (도메인별 - records)
└── student-record-write/ (도메인별 - records)

문제점

  • 공통 UI 컴포넌트와 도메인별 컴포넌트가 섞여있었습니다.
  • 컴포넌트의 성격(재사용성, 범위)을 파악하기 어려웠습니다.
  • 폴더가 많아지면서 필요한 파일을 찾는게 시간도 오래 걸리고, 찾는데 어려움이 커졌습니다.

2. Fetch 로직이 있는 service코드와 Tanstack-Query로 추상화한 hooks 코드의 분리

현재 구조:
hooks/
├── api/              (API 관련 훅만)
│   ├── auth/
│   ├── notice/
│   └── ...
├── use-mobile.tsx    (일반 훅)
└── use-auto-resize-textarea.tsx (일반 훅)

├── services/         API 서비스 (도메인별 분리)
│   ├── auth/
│   ├── notice/
│   ├── profile/
│   ├── student-manage/
│   └── write-record/

문제점

  • 기존에는 fetch 로직의 코드가 길어지고 응답 파싱, BaseURL 처리 등으로 코드가 길어지면서 관심사 분리가 필요하다고 생각했고, services 폴더 내의 fetch로직과 tanstack-query로 감싸는 코드를 분리했었는데, services 폴더 내의 fetch 로직이 공통 fetch로직 구현으로 간소화되면서 코드를 나누는게 복잡성이 더욱 증가했습니다.
  • 서버 컴포넌트에서는 services 폴더에 api 로직이 있고, 클라이언트 컴포넌트에서는 hooks/api 폴더에 api 로직이 있어 api 관련 로직을 찾는게 불편함이 증가했습니다.
  • use-mobile같은 공통 훅과 도메인별 훅의 구분이 없었습니다.

3. 확장성에 좋지 않은 문제

현재 도메인들:
- auth (인증)
- profile (프로필)
- notice (공지사항)
- write-record (생활기록부 작성)
- student-manage (학생 관리)

예정된 추가 기능:
- community (커뮤니티)
- admin (관리자)

문제점:

  • 현재 구조로는 새 도메인 추가시 components/, hooks/, services/ 등 여러 폴더에 흩어졌습니다.
  • 기존 역할 기반의 폴더 구조에서 하나의 components 폴더에 너무 많은 파일들이 생길 우려가 생겼습니다.
  • 특정 기능 제거시 관련 파일들을 여러 폴더에서 찾아야 했습니다.


🎯 핵심 개선 방향

1. Domain-First 구조로 전환

이유:

  • 기능별 폴더 분리가 아니라 비즈니스 도메인 중심으로 구성하면, 기능이 확장되더라도 관련 코드가 한 폴더에 모여 있어 수정하기 쉬워진다고 판단했습니다.
  • 코드를 리팩토링할 때 “이 기능이 어디 있는지”를 찾는 시간이 줄어듭니다.
  • 코드 변경 범위가 도메인 내부에 국한되어 사이드 이펙트가 최소화됩니다.

2. shared 레이어 분리

이유:

  • 전역적으로 재사용되는 UI 컴포넌트, 훅, 유틸리티를 한 곳에 모아두면 중복 개발을 방지한다고 판단했습니다.
  • 모든 도메인에서 공통 자원을 가져다 쓸 수 있으므로, UI와 비즈니스 로직의 결합도를 낮추고 재사용성을 높입니다.

3. API 로직 응집화

이유:

  • 기존에는 API 로직이 여러 곳에 흩어져 있어 API가 변경되면 2개의 파일에서 수정이 일어나 유지보수가 어려웠지만, 도메인 내부의 apis 폴더에서 mutations, queries, server-컴포넌트 API로 나누면 책임이 명확해진다고 판단했습니다.
  • 서버 컴포넌트에서 호출하는 API와 클라이언트 훅에서 쓰는 API를 구분하고, 하나의 파일에서 관리하면서 모든 api 로직을 다루면서 코드 구조가 깔끔해집니다.

📂 개선된 프로젝트 폴더 구조

EduMate-FE/
├── src/
│   ├── app/                           # Next.js App Router 페이지
│   │   ├── (auth)/                    # 인증 관련 페이지 그룹
│   │   │   ├── login/                 # 로그인 페이지
│   │   │   ├── ...
│   │   │   └── layout.tsx             # 인증 페이지 공통 레이아웃
│   │   │
│   │   ├── (main)/                    # 인증 관련 페이지를 제외한 모든 페이지 그룹
│   │   │   ├── manage-subject/        # 교과 기록 관리
│   │   │   ├── ...
│   │   │   ├── layout.tsx             # 기록 페이지 공통 레이아웃
│   │   │   └── page.tsx               # 메인 대시보드
│   │   │
│   │   ├── globals.css                # 전역 CSS 스타일
│   │   ├── layout.tsx                 # 루트 레이아웃
│   │   └── metadata.ts                # SEO 메타데이터
│   │
│   ├── domains/                       # 도메인별 비즈니스 로직
│   │   ├── auth/                      # Auth 도메인
│   │   │   ├── apis/                  # Auth API 로직
│   │   │   ├── components/            # Auth 컴포넌트
│   │   │   ├── constants/             # Auth 상수
│   │   │   ├── mocks/                 # Auth Mock 데이터
│   │   │   ├── __tests__/               # Auth 단위/통합 테스트
│   │   │   └── types/                 # Auth 타입 정의
│   │   │
│   │   ├── record/                    # 기록 도메인
│   │   ├── profile/                   # 프로필 도메인
│   │   └── notice/                    # 공지사항 도메인
│   │
│   └── shared/                        # 공통 모듈
│       ├── components/                
│       │   ├── layout/                # 레이아웃 컴포넌트
│       │   │   ├── header/            
│       │   │   └── sidebar/           
│       │   │
│       │   └── ui/                    # UI 기본 컴포넌트
│       │       ├── loading/                  
│       │       └── ...
│       ├── constants/                 # 전역 상수
│       ├── hooks/                     # 커스텀 훅
│       ├── lib/                       # 프로젝트 설정
│       ├── mocks/                     # Mock 설정
│       ├── providers/                 # Provider
│       ├── types/                     # 전역 타입 정의
│       └── utils/                     # 유틸리티 함수
│ 
└──tests/                              # e2e 테스트 파일

🔍 도메인별 세부 구조

auth 도메인

domains/auth/
├── apis/                  # Auth API 로직
├── components/            # Auth 컴포넌트
├── constants/             # Auth 상수
├── mocks/                 # Auth Mock 데이터
├── __tests__/             # Auth 단위/통합 테스트
└── types/                 # Auth 타입 정의

shared 모듈

shared/
├── components/          # UI 컴포넌트 (editor, sidebar 등)
├── constants/           # 전역 상수 (sidebar-config.ts 등)
├── hooks/               # 공유 훅
├── lib/                 # 공유 라이브러리
├── providers/           # Context 프로바이더
├── types/               # 공유 타입
└── utils/               # 유틸리티 함수


✨ 현재 구조의 개선된 점

1. 명확한 도메인 분리 및 공유 리소스 체계화

  • 비즈니스 도메인별로 관련 파일을 한 폴더에 모아두어, 기능 위치를 직관적으로 파악할 수 있습니다.

  • 공통으로 사용하는 컴포넌트·훅·유틸리티를 shared 레이어로 분리해 재사용성과 유지보수성을 높였습니다.

2. components 폴더를 각 도메인 별로 나누고, apis 폴더에 api와 관련된 모든 로직이 들어있어 수정사항이나 변경점에 대응하기 쉬워짐

  • 기존에 components에 모든 컴포넌트가 들어있는데, 공통 컴포넌트, layout, 도메인 컴포넌트별로 나누면서 파일을 찾기가 쉬워졌습니다.
  • 기존에 services와 hooks로 나뉘어져 있던 로직을 하나의 파일에서 관리하면서 수정사항에 대응하기 쉬워졌습니다.
  • 서버 컴포넌트에서 사용하는 api로직과 클라이언트 컴포넌트에서 사용하는 api로직이 같은 폴더에 있어 도메인 별로 api를 구현 및 수정하기 편해졌습니다.

3. 확장 가능한 구조

  • 새로운 기능을 추가할 때, 기존 코드와의 의존성을 최소화하여 독립적으로 개발할 수 있습니다.
  • 장기적으로 프로젝트가 커져도, 복잡도가 증가하는 것을 막아줍니다.


🔧 폴더 구조 리팩토링 후기

폴더 구조를 리팩토링함에 있어 다양한 레포지토리를 찾아보고, 여러 글을 읽어보면서 느낀 점은 "폴더 구조에 정답은 없다" 입니다.


대신 프로젝트 초기에 잘 선택하는 것이 중요하다는 느낌도 받았는데, 개발이 어느정도 끝나고 리팩토링하려고 하니 대규모 공사라 벌어져서 시간도 꽤 오래 걸리고, 리팩토링 후 기능이 잘 동작하는지를 확인해야 했습니다.

domain 중심, page 중심, FSD 아키텍쳐, 역할 기반의 폴더 구조 등등 여러 폴더 구조가 있었는데, 가장 중요한 것은 프로젝트에 맞는 아키텍쳐를 선택하고 설계하는 것임을 느꼈습니다!!


📚 참고 자료

https://velog.io/@teo/folder-structure
https://toss.tech/article/firesidechat_frontend_10
https://sennieworld.tistory.com/67![](https://velog.velcdn.com/images/lcs3623/post/f7b9cebc-a027-4fde-8c3c-196f44b01da7/image.png)

profile
소통하며 성장하는 프론트엔드 개발자 이승섭입니다! 👋

0개의 댓글