Next.js 프레임워크를 사용한 프로젝트를 몇 개 진행하다 보니, 자연스럽게 반복해서 마주치게 되는 고민들이 있었다.
- 폴더 구조를 어떻게 잡아야 유지보수가 쉬울까?
- 어디까지를 서버 컴포넌트로 두고, 어디서부터 클라이언트 컴포넌트로 분리해야 할까?
특히 App Router가 도입되면서 기존의 pages 기반 구조보다 자유도가 훨씬 높아졌고, 그만큼 팀 혹은 개인의 기준이 더 중요해졌다고 느꼈다. 이 글에서는 Next.js 공식 문서를 기반으로, 내가 실제 프로젝트(COTATO 동아리 리쿠르팅 홈페이지)에 적용한 구조와 그 이유, 그리고 그로 인해 얻은 장점들을 정리해보려고 한다.

1. Next.js App Router에서의 기본 구조
Next.js 공식 문서에서는 App Router 기준으로 다음과 같은 구조를 권장한다.
최상위 폴더 / 파일
- app/ : 라우팅의 중심이 되는 폴더
- public/ : 정적 자원
- next.config.js : Next.js 설정
- package.json
app 라우팅 관례
app 디렉토리 내부에서는 폴더 = 라우트 세그먼트가 된다.
- page.tsx : 실제 페이지
- layout.tsx : 해당 라우트 이하에 공통으로 적용되는 레이아웃
- loading.tsx, error.tsx, not-found.tsx
- (group) : URL에 영향을 주지 않는 라우트 그룹
공식 문서는 이 정도의 가이드만 제공하고, 도메인 단위의 세부 구조는 개발자에게 위임한다.
그래서 여기서부터는 프로젝트 특성에 맞는 폴더 구조 전략을 세우는 것이 중요해진다.
2. 내가 세운 큰 규칙 2가지
규칙 1. 페이지(page.tsx)는 클라이언트 컴포넌트로 선언하지 않는다
page.tsx에서 use client 선언하지 않기
- 페이지는 가능한 한 서버 컴포넌트로 유지한다.
- 서버에서 데이터 패칭 → 필요한 경우에만 클라이언트 컴포넌트로 위임한다.
이유
- 초기 렌더링 성능 개선
- SEO에 유리
- 불필요한 번들 사이즈 증가 방지
- 서버/클라이언트 경계가 명확해짐
상태 관리, 이벤트 처리, 브라우저 API가 필요한 경우에만 하위 컴포넌트를 use client로 분리한다.
규칙 2. 도메인 단위 컴포넌트는 해당 도메인 내부에 둔다
application-edit
├ _components
├ _containers
├ page.tsx
- 특정 도메인에서만 사용하는 컴포넌트는 그 도메인 폴더 안에 위치
- 공용 컴포넌트만 shared, common 성격의 위치로 분리
이유
- 컴포넌트의 사용 범위가 폴더 구조만 봐도 드러남
- 나중에 기능을 삭제하거나 수정할 때 영향 범위를 예측하기 쉬움
- 도메인 단위 사고(Domain-driven thinking)에 도움이 됨
3. 폴더 분리 원칙 (COTATO 리쿠르팅 홈페이지 사례)
현재 진행 중인 COTATO 동아리 리쿠르팅 홈페이지의 구조를 예로 들어보자.
app
├ (home)
├ (with-footer)
├ admin
│ ├ _components
│ ├ (no-sidebar)
│ ├ (with-sidebar)
│ │ ├ application-edit
│ │ │ ├ _components
│ │ │ ├ _containers
│ │ │ └ page.tsx
│ │ ├ applications
│ │ ├ recruitment
│ │ ├ results
│ │ └ layout.tsx
├ apply
└ oauth2
1) 라우트 그룹 적극 활용
- (home), (with-footer), (with-sidebar) 등은 URL에는 드러나지 않지만 구조를 명확히 하기 위한 그룹
- 레이아웃 단위로 화면 책임을 분리
장점
- URL 구조는 단순하게 유지
- 레이아웃 변경 시 영향 범위가 명확
2) admin 도메인의 계층적 분리
- admin 자체가 하나의 큰 도메인
- 사이드바 유무에 따라 (with-sidebar), (no-sidebar)로 분리
- layout.tsx에서 어드민 공통 UI 관리
장점
- 어드민 UI 정책이 한 곳에 모임
- 새로운 어드민 페이지 추가 시 구조 고민 최소화
3) _components vs _containers 분리
_components
- 순수 UI 컴포넌트
- props 기반
- 데이터 패칭 로직 없음
_containers
- React Query, 데이터 패칭, mutation 로직
- 비즈니스 로직 담당
- 필요 시 클라이언트 컴포넌트
application-edit
├ _components // View
├ _containers // Logic
└ page.tsx // Composition
장점
- UI와 로직의 관심사 분리
- 테스트 및 리팩토링이 쉬움
- 컴포넌트 재사용성이 높아짐
4. 서버 컴포넌트 / 클라이언트 컴포넌트 분리 기준
내가 정리한 기준은 다음과 같다.
1) 서버 컴포넌트로 두는 경우
- 페이지 (page.tsx)
- 데이터 패칭이 중심인 컴포넌트
- SEO가 중요한 영역
- 정적인 UI
2) 클라이언트 컴포넌트로 분리하는 경우
- 사용자 인터랙션 필요
- 상태 관리 (폼, 모달, 토글 등)
- React Query, Zustand 등 클라이언트 상태 사용
- 브라우저 API 사용
5. 이 구조를 적용하며 느낀 장점
- 프로젝트 규모가 커져도 구조가 쉽게 무너지지 않는다
- 새로 합류한 사람이 구조를 이해하기 쉽다
- 페이지가 서버 컴포넌트로 유지되어 성능 이점
- 도메인 단위로 책임이 명확해진다
- 폴더 구조 자체가 문서 역할을 한다
마무리
Next.js App Router는 정답을 강요하지 않는 대신, 개발자에게 구조 설계의 책임을 넘긴다. 그래서 초반에 명확한 기준을 세우는 것이 정말 중요하다고 느꼈다.
이 글에서 정리한 방식이 모든 프로젝트에 정답은 아니지만,
- 중대형 규모
- 도메인 분리가 필요한 서비스
- 협업을 염두에 둔 프로젝트
라면 충분히 한 번쯤 고려해볼 만한 구조라고 생각한다.
나중에 프로젝트가 더 커지면 이 구조가 어떻게 진화했는지도 다시 정리해 볼 예정이다.
'프론트엔드' 카테고리의 다른 글
| 모노레포에서 미디어 쿼리가 무시되는 이유 (0) | 2026.03.02 |
|---|---|
| @use-funnel로 복잡한 온보딩 흐름 제어하기 (feat. Zod) (1) | 2026.02.23 |
| 프로젝트 통합으로 생산성 높이기: Turborepo 도입 및 모노레포 마이그레이션 여정 (0) | 2026.02.22 |
| Next.js 프로젝트에서 Storybook 도입해보기 (0) | 2026.01.29 |
| 시각장애인을 위한 모달 컴포넌트 웹 접근성 개선기 (Focus Trap 적용기) (0) | 2026.01.29 |