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
현재 상황:
components/
├── header/ (공통 레이아웃)
├── input/ (공통 UI)
├── modal/ (공통 UI)
├── login/ (도메인별 - auth)
├── signup/ (도메인별 - auth)
├── mypage/ (도메인별 - profile)
├── notice/ (도메인별 - notice)
├── record/ (도메인별 - records)
└── student-record-write/ (도메인별 - records)
문제점
- 공통 UI 컴포넌트와 도메인별 컴포넌트가 섞여있었습니다.
- 컴포넌트의 성격(재사용성, 범위)을 파악하기 어려웠습니다.
- 폴더가 많아지면서 필요한 파일을 찾는게 시간도 오래 걸리고, 찾는데 어려움이 커졌습니다.
현재 구조:
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같은 공통 훅과 도메인별 훅의 구분이 없었습니다.
현재 도메인들:
- auth (인증)
- profile (프로필)
- notice (공지사항)
- write-record (생활기록부 작성)
- student-manage (학생 관리)
예정된 추가 기능:
- community (커뮤니티)
- admin (관리자)
문제점:
- 현재 구조로는 새 도메인 추가시 components/, hooks/, services/ 등 여러 폴더에 흩어졌습니다.
- 기존 역할 기반의 폴더 구조에서 하나의 components 폴더에 너무 많은 파일들이 생길 우려가 생겼습니다.
- 특정 기능 제거시 관련 파일들을 여러 폴더에서 찾아야 했습니다.
이유:
이유:
이유:
mutations, queries, server-컴포넌트 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 테스트 파일
domains/auth/
├── apis/ # Auth API 로직
├── components/ # Auth 컴포넌트
├── constants/ # Auth 상수
├── mocks/ # Auth Mock 데이터
├── __tests__/ # Auth 단위/통합 테스트
└── types/ # Auth 타입 정의
shared/
├── components/ # UI 컴포넌트 (editor, sidebar 등)
├── constants/ # 전역 상수 (sidebar-config.ts 등)
├── hooks/ # 공유 훅
├── lib/ # 공유 라이브러리
├── providers/ # Context 프로바이더
├── types/ # 공유 타입
└── utils/ # 유틸리티 함수
비즈니스 도메인별로 관련 파일을 한 폴더에 모아두어, 기능 위치를 직관적으로 파악할 수 있습니다.
공통으로 사용하는 컴포넌트·훅·유틸리티를 shared 레이어로 분리해 재사용성과 유지보수성을 높였습니다.
폴더 구조를 리팩토링함에 있어 다양한 레포지토리를 찾아보고, 여러 글을 읽어보면서 느낀 점은 "폴더 구조에 정답은 없다" 입니다.
domain 중심, page 중심, FSD 아키텍쳐, 역할 기반의 폴더 구조 등등 여러 폴더 구조가 있었는데, 가장 중요한 것은 프로젝트에 맞는 아키텍쳐를 선택하고 설계하는 것임을 느꼈습니다!!
https://velog.io/@teo/folder-structure
https://toss.tech/article/firesidechat_frontend_10
https://sennieworld.tistory.com/67