FE
[Next.js] 실무에서 쓰는 Next.js 구조 설계 – src, app, features, components
프로젝트가 커질수록 이런 경험, 한 번쯤은 해봤을 것이다.
app/, components/, utils/ 안에 파일이 정체 불명의(name.conflict)처럼 쌓이다 보니 "이걸 어디에 넣어야 하지?" 며 5분을 검색하게 된다. 누구나 겪는 이 문제는 단순한 디렉토리 정리가 아니다.
Next.js 공식 문서는 “폴더와 파일 구조는 자유지만, 제대로 설계하면 유지보수와 협업에 큰 도움이 된다”고 조언한다. 실제 개발자들도 공감하는데, 다음은 Reddit에서 나온 이야기다:
“I use a feature‑based folder structure to keep things organized and scalable.”
커뮤니티 한 회원은 기능 단위로 폴더를 나누는 구조 덕분에 “집중해서 개발하고, 나중에 확장하기도 쉬워졌다”고 평가했다. 이런 배경 안에서 이 글은 실무 환경에서 확장성과 협업을 고려한 Next.js + TypeScript + Tailwind 기반 구조 설계 전략을 단계별로 제시하고자 한다.
1. src 및 app 디렉토리 구조
Next.js 13 이상에서는 app/ 폴더를 사용하여 라우팅 구조를 정의하고, src/ 내부에서 애플리케이션 코드를 관리하는 패턴이 권장된다.
공식 문서에서는 아래와 같은 구조를 제안한다 (nextjs.org, sentry.io):
src/
├── app/
│ ├── layout.tsx
│ ├── page.tsx
│ └── ...
├── components/
├── lib/
├── utils/
├── hooks/
├── types/
└── styles/2. app/ 내부 구조 설계
폴더 이름이 URL 경로가 된다.
(group)/ 형식의 Route Group을 사용하면 URL 경로에 영향을 주지 않고 기능별 묶음 가능 (nextjs.org).
_private/ 언더스코어 폴더를 사용하면 라우팅에서 제외되는 Private Folder 역할 수행 (nextjs.org).
Next.js의 app/ 디렉토리 구조에서는 언더스코어(_)로 시작하는 폴더를 라우팅에서 자동으로 제외한다. 즉, app/_components/처럼 폴더명을 _로 시작하면 URL 경로에는 포함되지 않고, 해당 경로를 내부적으로만 사용할 수 있다.
간단히 말해, _components, _lib, _layouts 같은 폴더는 클린한 라우팅 구조를 유지하면서도 기능별 정리를 가능하게 해주는 유용한 구조이다.
app/
├── layout.tsx
├── page.tsx
├── (dashboard)/
│ ├── page.tsx
│ └── _components/
└── blog/
├── page.tsx
├── [slug]/
│ └── page.tsx
└── metadata.ts3. 기능(feature)-기반 모듈 구조
대형 프로젝트에서는 Feature 기반 구조를 사용하는 것이 유지보수와 확장성 측면에서 유리하다 (reddit.com).
예시:
src/features/
└── cart/
├── Cart.tsx
├── CartItem.tsx
├── useCart.ts
└── cart.types.tsReddit 실무자들은 다음과 같이 평가했다:
4. 재사용 UI 컴포넌트 분리
components/ui/: 버튼, 모달, 입력 등 범용 컴포넌트 저장
components/features/: 특정 feature에 종속된 UI 저장
UI와 로직은 철저히 분리하여 모듈화 (wisp.blog, robinwieruch.de)
5. 훅, 라이브러리, 유틸, 상태관리 분리
hooks/: 커스텀 훅 (useForm, useAuth 등)
lib/: API 호출, 외부 서비스 래퍼, DB 연결
utils/: 날짜·문자열 포맷, 도우미 함수
store/: Zustand, Redux 등의 전역 상태관리 로직
types/: 공통 TypeScript 타입 정의 (dev.to, wisp.blog, nextjsstarter.com)
6. 실제 예시 구조 (중대형 프로젝트)
다음은 실무에 바로 적용 가능한 예시 구조이다:
src/
├── app/
│ ├── layout.tsx
│ ├── page.tsx
│ ├── (auth)/
│ │ ├── login/page.tsx
│ │ └── register/page.tsx
│ └── dashboard/
│ ├── layout.tsx
│ ├── page.tsx
│ └── settings/page.tsx
├── components/
│ ├── ui/
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.test.tsx
│ │ │ └── index.ts
│ │ └── Modal/
│ └── features/
│ └── blog/
│ ├── PostCard/
│ └── CategoryList/
├── features/
│ └── cart/
│ ├── Cart.tsx
│ ├── cart.types.ts
│ └── useCart.ts
├── hooks/
│ ├── useForm.ts
│ └── useMediaQuery.ts
├── lib/
│ └── api/
│ └── getBlogPosts.ts
├── utils/
│ └── formatting.ts
├── store/
│ └── authStore.ts
├── types/
│ └── blog.types.ts
└── styles/
└── globals.css해당 예시는 “The Ultimate Guide to Organizing Your Next.js 15 Project Structure”에서 제시된 중대형 구조를 참고한 것이다 (wisp.blog).
7. 깊이 및 colocaton 원칙
1) 컴포넌트 깊이 3~4레벨 이내 유지 권장 .
프로젝트가 커질수록 폴더 안에 폴더, 또 그 안에 폴더… 이렇게 너무 깊게 구조화되면 오히려 찾기도 어렵고, import 경로도 복잡해진다.
예를 들어 이런 경로는 좋지 않은 예이다:
src/components/ui/buttons/primary/desktop/variant1/Button.tsx이처럼 5단계 이상 깊어지면 유지보수가 어려워지므로, 일반적으로는 3~4단계 이내로 폴더 깊이를 제한하는 것이 좋다:
src/components/Button/Button.tsx ✅ src/features/blog/PostCard.tsx ✅즉, 찾기 쉽고, import 경로가 짧을수록 좋다.
2) Colocation 원칙: 관련된 파일은 가까이 두자
Colocation(콜로케이션)이란, 서로 연관된 코드끼리 같은 폴더에 배치하는 것을 의미한다. 이를 통해 이해와 관리가 훨씬 쉬워진다.
예를 들어, 특정 컴포넌트에만 사용하는 스타일, 타입, 훅이 있다면 이렇게 정리할 수 있다:
src/components/PostCard/
├── PostCard.tsx
├── PostCard.module.css
├── usePostCard.ts
└── postCard.types.ts이렇게 하면 PostCard 컴포넌트에 관련된 모든 파일이 한눈에 들어오고, 다른 곳에 영향 없이 유지보수할 수 있다.
8. Barrel 파일 (index.ts) 활용
components/ui/Button/index.ts처럼 index 파일을 활용하면 import 경로를 간단하게 정리할 수 있다 .
1) Barrel 파일이란?
index.ts 파일은 하나의 폴더 안에서 여러 파일을 한꺼번에 export할 수 있도록 도와주는 역할을 한다.
이런 방식은 import 경로를 깔끔하게 줄여주는 효과가 있다.
2) ❌ 일반적인 import 방식 (Barrel 파일 없이)
import { Button } from '@/components/ui/Button/Button'; import { ButtonIcon } from '@/components/ui/Button/ButtonIcon';파일마다 경로를 직접 작성해야 하니, 경로가 길고 반복적이다.
3) ✅ Barrel 파일을 사용하는 방식 (index.ts 활용)
// components/ui/Button/index.ts export * from './Button'; export * from './ButtonIcon';이렇게 export만 정리해두면, 다음과 같이 경로가 훨씬 짧고 간단해진다:
import { Button, ButtonIcon } from '@/components/ui/Button';4) ? 구조 예시
components/
└── ui/
└── Button/
├── Button.tsx
├── ButtonIcon.tsx
└── index.ts ← 여기서 export 처리9. README에 구조 문서화
프로젝트 최상위에 README.md를 작성하여 폴더 구조, 네이밍 규칙, alias 사용법 등을 명시하고 팀원과 공유하자.
마무리 제안
폴더 구조는 프로젝트의 규모, 팀 구성, 목적에 따라 유동적으로 변화해야 한다. 위에서 소개한 구조는 중대형 규모의 Next.js 실무 환경에서 검증된 방식이지만, 모든 프로젝트에 정답이 될 수는 없다. 중요한 것은 팀이 함께 이해하고 유지할 수 있는 구조를 만드는 것이다.
처음부터 완벽할 필요는 없다. 명확한 기준과 일관된 구조를 갖추는 것만으로도 협업 효율과 생산성은 놀랄 만큼 향상된다.