Next.js는 현대적인 웹 애플리케이션 개발을 위한 강력한 도구입니다. 특히 프로젝트가 커지고 협업이 많아질수록 폴더 구조의 중요성이 부각됩니다. Next.js에서 효과적인 폴더 구조를 사용하면 유지보수성과 확장성이 크게 향상됩니다. 이 글에서는 Next.js의 폴더 구조를 설계하고 최적화하는 방법을 자세히 살펴보겠습니다.
프로젝트가 커질수록 코드의 양과 복잡도가 증가합니다. 체계적인 폴더 구조는 코드 탐색과 수정 작업을 수월하게 만들어줍니다.
모든 파일이 적절히 분류되어 있으면, 디버깅과 리팩토링 과정에서도 효율성이 높아집니다.
일관된 폴더 구조는 팀원 간 코드 공유와 작업 분배를 원활하게 합니다.
새로운 팀원이 합류해도 구조가 명확하면 적응 속도가 빨라집니다.
공통으로 사용되는 컴포넌트와 유틸리티를 별도로 분리하면 코드 재사용성이 높아집니다.
기능별로 코드를 분리하면 새로운 기능 추가 시 충돌을 최소화할 수 있습니다.
Next.js 13 이상 버전에서는 App Router를 도입하여 파일 및 폴더 이름만으로 라우팅을 설정할 수 있습니다. 이 새로운 방식은 개발자의 생산성을 크게 향상시키며, 코드베이스를 더 깔끔하게 유지할 수 있습니다.
예시 디렉터리 구조:
app/
├─ home/
│ ├─ page.tsx # /home
│ ├─ homeBanner.tsx # not-routable
├─ about/
│ ├─ page.tsx # /about
├─ api/
│ ├─ hello/
│ │ └─ route.ts # /api/hello (API 라우트)
│ │ └─ GET.ts # not-routable
언더스코어(_)로 시작하는 폴더는 라우팅 시스템에서 제외됩니다.
app/
├─ _components/ # 라우팅되지 않음
└─ _settings/ # 라우팅되지 않음
폴더 이름을 괄호로 감싸면 URL 경로에는 영향을 미치지 않고, 파일 구조를 조직화할 수 있습니다.
// 어드민 분리
app/
├─ (admin)/
│ ├─ dashboard/
│ │ ├─ page.tsx # /dashboard 로 매핑
// 데스크탑 모바일 분리
app/
├─ (desktop)/
│ ├─ dashboard/
│ │ ├─ page.tsx # /dashboard 로 매핑
├─ m/
│ ├─ dashboard/
│ │ ├─ page.tsx # /dashboard 로 매핑
page.tsx 또는 route.ts가 없는 폴더는 라우팅되지 않으므로, 그 안에 다른 파일을 안전하게 배치할 수 있습니다.
src 디렉터리를 활용하면 루트 디렉터리를 정리할 수 있습니다.
tsconfig.json에서 경로 별칭(@/)을 설정하여 import 경로를 단축할 수 있습니다.
경로 별칭 설정 예시:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/components/*": ["components/*"],
"@/utils/*": ["utils/*"]
}
}
}
사용 시 이점
// 별칭 미사용 시
import Button from '../../../components/common/Button';
// 별칭 사용 시
import Button from '@/components/common/Button';
특징: Page.tsx를 제외한 모든 컴포넌트가 components/ 폴더에 모여 있습니다.
구조 예시
my-app/
├─ app/
│ ├─ home/
│ │ ├─ page.tsx
│ ├─ about/
│ │ ├─ page.tsx
├─ components/
│ ├─ Button.tsx
│ ├─ Header.tsx
│ ├─ Footer.tsx
├─ package.json
└─ README.md
장점: 구조가 단순하고 설정이 빠릅니다.
단점: 컴포넌트 수가 많아질 경우 관리가 어려워질 수 있습니다.
특징: 공통 컴포넌트, 레이아웃, UI 등을 기능적으로 분류합니다
my-app/
├─ app/
│ ├─ home/
│ │ ├─ page.tsx
│ ├─ about/
│ │ ├─ page.tsx
├─ components/
│ ├─ common/
│ │ ├─ Button.tsx
│ │ ├─ Icon.tsx
│ ├─ layout/
│ │ ├─ Header.tsx
│ │ ├─ Footer.tsx
│ ├─ forms/
│ │ ├─ LoginForm.tsx
│ │ ├─ ContactForm.tsx
│ ├─ ui/
│ │ ├─ Modal.tsx
│ │ ├─ Tabs.tsx
├─ package.json
└─ README.md
장점: 파일 검색과 관리가 용이하며, 협업 시 목적을 명확히 전달할 수 있습니다.
단점: 명확히 구분하기 애매한 컴포넌트가 생길 수 있습니다.
my-app/
├─ app/
│ ├─ user/
│ │ ├─ page.tsx
│ ├─ product/
│ │ ├─ page.tsx
├─ components/
│ ├─ user/
│ │ ├─ UserProfile.tsx
│ │ ├─ UserList.tsx
│ ├─ product/
│ │ ├─ ProductDetail.tsx
│ │ ├─ ProductList.tsx
│ ├─ blog/
│ │ ├─ BlogPost.tsx
│ │ ├─ BlogList.tsx
장점: 한 기능에 관련된 모든 코드를 한 곳에서 관리.
단점: 공통 로직 중복 가능성.
특징: UI를 원자(Atoms)부터 유기체(Organisms)까지 단계적으로 조합합니다.
my-app/
├─ app/
│ ├─ home/
│ │ ├─ page.tsx
├─ components/
│ ├─ atoms/
│ │ ├─ Button.tsx
│ │ ├─ Input.tsx
│ ├─ molecules/
│ │ ├─ SearchBar.tsx
│ │ ├─ UserCard.tsx
│ ├─ organisms/
│ │ ├─ Header.tsx
│ │ ├─ Footer.tsx
├─ package.json
└─ README.md
장점: 재사용성과 일관성 극대화.
단점: Atomic Design 개념을 이해해야 하며, 모든 컴포넌트를 정확히 분류하기 어렵습니다.
페이지에서만 사용하는 컴포넌트를 _component 폴더에 배치합니다.
폴더구조예시
app/
├─ home/
│ ├─ page.tsx
│ ├─ _component/
│ │ ├─ HomeBanner.tsx
│ │ ├─ HomeCTA.tsx
├─ about/
│ ├─ page.tsx
│ ├─ _component/
│ │ ├─ TeamSection.tsx
│ │ ├─ AboutHero.tsx
팀 내 폴더와 파일명 규칙을 문서화하여 공유.
폴더 내 index.ts 파일을 사용하여 하위 컴포넌트를 일괄 export.
예시 디렉터리 구조
my-app/
├─ components/
│ ├─ common/
│ │ ├─ Button.tsx
│ │ ├─ Icon.tsx
│ │ └─ index.ts
│ ├─ forms/
│ │ ├─ LoginForm.tsx
│ │ └─ index.ts
common/index.ts 내용 예시
export { default as Button } from './Button';
export { default as Icon } from './Icon';
forms/index.ts 내용 예시
export { default as LoginForm } from './LoginForm';
사용 시 이점
간단한 Import 경로
// 기존 방식
import Button from '@/components/common/Button';
import Icon from '@/components/common/Icon';
// index 파일 사용 시
import { Button, Icon } from '@/components/common';
my-app/
├─ app/
│ ├─ api/
│ │ ├─ hello/
│ │ │ └─ route.ts # '/api/hello' (API 라우트)
│ ├─ page.tsx # '/' 경로
│ ├─ (admin)/ # 라우터 그룹 (URL 경로 포함 X)
│ │ ├─ dashboard/
│ │ │ ├─ page.tsx # '/dashboard' 경로
│ │ ├─ settings/
│ │ │ ├─ page.tsx # '/settings'
│ ├─ auth/
│ │ ├─ login/
│ │ │ ├─ page.tsx # '/auth/login'
│ │ ├─ signup/
│ │ │ ├─ page.tsx # '/auth/signup'
│ ├─ user/
│ │ ├─ page.tsx # '/user'
│ │ ├─ _component/ # 해당 라우트 내 전용 컴포넌트
│ │ │ ├─ UserProfile.tsx
│ │ │ ├─ ActivityChart.tsx
│
├─ _components/ # 전역 컴포넌트
│ ├─ common/
│ │ ├─ Button.tsx
│ │ ├─ Icon.tsx
│ │ └─ index.ts
│ ├─ layout/
│ │ ├─ Header.tsx
│ │ ├─ Footer.tsx
│ │ └─ index.ts
│ ├─ ui/
│ │ ├─ Modal.tsx
│ │ ├─ Tabs.tsx
│ │ └─ index.ts
│ ├─ forms/
│ │ ├─ LoginForm.tsx
│ │ └─ index.ts
│
├─ queries
├─ lib/ # 비즈니스 로직/도메인 로직
│ ├─ auth.ts # 예: JWT 처리 로직
│ ├─ payments.ts # 예: 결제 로직
│ └─ ...
├─ hooks/ # 커스텀 훅
│ ├─ useAuth.ts
│ ├─ useInfiniteScroll.ts
│ └─ ...
├─ store/ # 전역 상태 관리 (Redux, Zustand, etc.)
│ ├─ index.ts
│ ├─ userSlice.ts
│ └─ ...
├─ utils/ # 범용 유틸
│ ├─ formatDate.ts
│ ├─ formatNumber.ts
│ └─ ...
├─ styles/ # 전역 스타일
│ ├─ globals.css
│ └─ variables.css
├─ public/
│ ├─ images/
│ │ ├─ logo.png
│ │ └─ banner.jpg
│ └─ favicon.ico
├─ package.json
├─ tsconfig.json
└─ .env.local