프론트엔드

Next.js App router 프로젝트 구조 활용 전략

김민나 2026. 1. 29. 20:23

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. 이 구조를 적용하며 느낀 장점 

  1. 프로젝트 규모가 커져도 구조가 쉽게 무너지지 않는다
  2. 새로 합류한 사람이 구조를 이해하기 쉽다
  3. 페이지가 서버 컴포넌트로 유지되어 성능 이점
  4. 도메인 단위로 책임이 명확해진다
  5. 폴더 구조 자체가 문서 역할을 한다

마무리

Next.js App Router는 정답을 강요하지 않는 대신, 개발자에게 구조 설계의 책임을 넘긴다. 그래서 초반에 명확한 기준을 세우는 것이 정말 중요하다고 느꼈다.

이 글에서 정리한 방식이 모든 프로젝트에 정답은 아니지만,

 

  • 중대형 규모
  • 도메인 분리가 필요한 서비스
  • 협업을 염두에 둔 프로젝트

 

라면 충분히 한 번쯤 고려해볼 만한 구조라고 생각한다.

나중에 프로젝트가 더 커지면 이 구조가 어떻게 진화했는지도 다시 정리해 볼 예정이다.