0197

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):

code
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 같은 폴더는 클린한 라우팅 구조를 유지하면서도 기능별 정리를 가능하게 해주는 유용한 구조이다.

code
app/
├── layout.tsx
├── page.tsx
├── (dashboard)/
│   ├── page.tsx
│   └── _components/
└── blog/
    ├── page.tsx
    ├── [slug]/
    │   └── page.tsx
    └── metadata.ts

3. 기능(feature)-기반 모듈 구조

대형 프로젝트에서는 Feature 기반 구조를 사용하는 것이 유지보수와 확장성 측면에서 유리하다 (reddit.com).

TypeScript
예시:

src/features/
└── cart/
    ├── Cart.tsx
    ├── CartItem.tsx
    ├── useCart.ts
    └── cart.types.ts

Reddit 실무자들은 다음과 같이 평가했다:

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. 실제 예시 구조 (중대형 프로젝트)

다음은 실무에 바로 적용 가능한 예시 구조이다:

code
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 경로도 복잡해진다.

예를 들어 이런 경로는 좋지 않은 예이다:

TypeScript
src/components/ui/buttons/primary/desktop/variant1/Button.tsx

이처럼 5단계 이상 깊어지면 유지보수가 어려워지므로, 일반적으로는 3~4단계 이내로 폴더 깊이를 제한하는 것이 좋다:

TypeScript
src/components/Button/Button.tsx ✅ src/features/blog/PostCard.tsx ✅

즉, 찾기 쉽고, import 경로가 짧을수록 좋다.

2) Colocation 원칙: 관련된 파일은 가까이 두자

Colocation(콜로케이션)이란, 서로 연관된 코드끼리 같은 폴더에 배치하는 것을 의미한다. 이를 통해 이해와 관리가 훨씬 쉬워진다.

예를 들어, 특정 컴포넌트에만 사용하는 스타일, 타입, 훅이 있다면 이렇게 정리할 수 있다:

TypeScript
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 파일 없이)

TypeScript
import { Button } from '@/components/ui/Button/Button'; import { ButtonIcon } from '@/components/ui/Button/ButtonIcon';

파일마다 경로를 직접 작성해야 하니, 경로가 길고 반복적이다.

3) ✅ Barrel 파일을 사용하는 방식 (index.ts 활용)

TypeScript
// components/ui/Button/index.ts export * from './Button'; export * from './ButtonIcon';

이렇게 export만 정리해두면, 다음과 같이 경로가 훨씬 짧고 간단해진다:

TypeScript
import { Button, ButtonIcon } from '@/components/ui/Button';

4) ? 구조 예시

TypeScript
components/
└── ui/
    └── Button/
        ├── Button.tsx
        ├── ButtonIcon.tsx
        └── index.ts   ← 여기서 export 처리

9. README에 구조 문서화

프로젝트 최상위에 README.md를 작성하여 폴더 구조, 네이밍 규칙, alias 사용법 등을 명시하고 팀원과 공유하자.

마무리 제안

폴더 구조는 프로젝트의 규모, 팀 구성, 목적에 따라 유동적으로 변화해야 한다. 위에서 소개한 구조는 중대형 규모의 Next.js 실무 환경에서 검증된 방식이지만, 모든 프로젝트에 정답이 될 수는 없다. 중요한 것은 팀이 함께 이해하고 유지할 수 있는 구조를 만드는 것이다.

처음부터 완벽할 필요는 없다. 명확한 기준과 일관된 구조를 갖추는 것만으로도 협업 효율과 생산성은 놀랄 만큼 향상된다.