Next.js 프로젝트 표준 디렉토리 구조는 무엇인가요?
Next.js 프로젝트의 표준 디렉토리 구조는 모든 팀에 똑같이 적용되는 하나의 정답 폴더 트리가 아니라, 프레임워크가 해석해야 하는 라우팅 파일은 정해진 규칙에 맞추고 나머지 코드는 프로젝트 성격에 맞게 배치하는 방식입니다. 새 프로젝트라면 보통 src/app을 URL과 화면 진입점의 중심으로 두고, 여러 곳에서 쓰는 UI는 components, 기능별 로직은 필요할 때 features, 공통 도구는 lib에 분리하는 구성이 이해하기 쉽습니다. App Router는 현재 Next.js의 주요 라우팅 방식이며, Pages Router도 계속 지원됩니다. nextjs.orgnextjs.org
처음 구조를 정할 때 중요한 것은 폴더 수를 많이 늘리는 일이 아닙니다. 어떤 파일이 URL을 만들고, 어떤 파일이 레이아웃·오류 화면·API를 담당하며, 어떤 코드는 특정 화면 안에서만 쓰이는지를 구분하는 일이 우선입니다. 이 글에서는 그 구분을 바탕으로 유지하기 쉬운 App Router 중심 구조를 살펴봅니다.
Next.js에 ‘유일한 표준 구조’가 없는 이유는 무엇인가요?
Next.js는 파일과 폴더를 바탕으로 경로를 구성하는 규칙을 제공하지만, 모든 컴포넌트와 비즈니스 로직을 어느 폴더에 두어야 하는지까지 강제하지는 않습니다. 특히 App Router에서는 폴더가 URL의 세그먼트를 표현하고, page.tsx나 route.ts 같은 특수 파일이 실제 공개 진입점을 만듭니다. 반대로 라우트 안에 둔 일반 파일은 기본적으로 외부 경로가 되지 않습니다. nextjs.org
이 특성 때문에 같은 Next.js 애플리케이션이라도 구조는 달라질 수 있습니다. 작은 소개 사이트는 app과 소수의 공용 컴포넌트만으로 충분할 수 있습니다. 반면 대시보드, 주문, 계정처럼 여러 도메인이 있는 서비스는 기능별 코드와 공용 코드를 구분하는 편이 변경 범위를 파악하기 쉽습니다. 이는 Next.js가 요구하는 절대 규칙이라기보다, 라우팅 규칙 위에 팀이 선택하는 코드 정리 방식입니다.
따라서 ‘표준’을 다음 두 층으로 나누어 생각하면 혼란이 줄어듭니다.
| 구분 | 성격 | 대표 예시 |
|---|---|---|
| Next.js가 해석하는 규칙 | 파일명과 위치가 동작에 직접 영향을 줍니다 | app/page.tsx, app/layout.tsx, app/api/users/route.ts |
| 프로젝트가 정하는 규칙 | 팀이 목적에 맞게 이름과 경계를 정합니다 | components, features, lib, hooks, types |
첫 번째 층은 임의로 바꾸면 라우팅이나 특수 UI 동작이 달라질 수 있습니다. 두 번째 층은 규모와 도메인 복잡도에 따라 생략하거나 합칠 수 있습니다. 예를 들어 features가 없는 구조도 가능하고, 작은 프로젝트에서 모든 코드를 지나치게 세분화할 필요도 없습니다.
새 프로젝트에서는 왜 App Router를 중심으로 생각하나요?
App Router는 app 디렉토리를 기반으로 페이지와 레이아웃을 구성하는 방식입니다. 공식 문서는 최신 React 기능을 활용하기 위해 Pages Router에서 App Router로 이전하는 방향을 권장하며, Pages Router 자체도 지원 대상으로 유지합니다. 그러므로 기존 프로젝트의 구조를 이해하거나 유지보수할 때는 pages 규칙을 알아야 하지만, 새 구조를 설계할 때는 App Router를 기본 후보로 두는 판단이 자연스럽습니다. nextjs.org
App Router에서 핵심은 URL 계층과 파일 계층을 연결하는 것입니다. 예를 들어 app/dashboard/page.tsx는 /dashboard 페이지의 진입점이 되고, 그 아래 app/dashboard/layout.tsx는 대시보드 하위 경로에 적용되는 레이아웃이 될 수 있습니다. 루트의 app/layout.tsx는 전체 라우트를 감싸는 루트 레이아웃입니다. nextjs.org
이 방식은 화면 단위 코드를 가까이 두는 데 유리합니다. /dashboard에만 필요한 탭, 표, 필터 UI를 app/dashboard 아래에 둘 수 있고, 여러 화면에서 재사용되는 버튼이나 입력 필드는 바깥의 공용 폴더로 옮길 수 있습니다. 중요한 기준은 ‘파일이 어느 기술로 작성됐는가’보다 ‘어느 범위에서 재사용되는가’입니다.
다만 App Router를 쓴다고 해서 모든 코드를 app 안에 넣어야 하는 것은 아닙니다. app은 URL과 특수 파일을 읽기 쉽게 보여 주는 경계로 두고, 공용 코드나 복잡한 기능 구현은 필요에 따라 별도 폴더로 분리할 수 있습니다. 이 분리는 Next.js가 자동으로 해 주는 작업이 아니라 팀이 일관되게 정하는 구조 규칙입니다.
src 폴더는 언제 쓰고 무엇을 루트에 남기나요?
src는 선택 사항입니다. 사용하면 app과 애플리케이션 소스 코드를 src 아래에 모아, 설정 파일과 실행 코드를 시각적으로 구분할 수 있습니다. 반면 public, package.json, next.config.js, tsconfig.json, .env.* 파일은 프로젝트 루트에 둡니다. nextjs.org
다음은 App Router와 src를 함께 쓸 때 이해하기 쉬운 한 가지 예시입니다.
my-app/
├─ public/
│ ├─ images/
│ └─ fonts/
├─ src/
│ ├─ app/
│ │ ├─ layout.tsx
│ │ ├─ page.tsx
│ │ ├─ globals.css
│ │ ├─ (marketing)/
│ │ │ └─ about/
│ │ │ └─ page.tsx
│ │ ├─ dashboard/
│ │ │ ├─ layout.tsx
│ │ │ ├─ page.tsx
│ │ │ ├─ loading.tsx
│ │ │ ├─ error.tsx
│ │ │ └─ _components/
│ │ └─ api/
│ │ └─ users/
│ │ └─ route.ts
│ ├─ components/
│ ├─ features/
│ ├─ lib/
│ ├─ hooks/
│ └─ types/
├─ .env.local
├─ next.config.js
├─ package.json
└─ tsconfig.json
이 예시에서 src는 애플리케이션 코드의 경계일 뿐, 동작 자체를 바꾸는 필수 장치는 아닙니다. 이미 루트에 app이 있는 프로젝트라면 무리하게 src를 추가하는 것보다 기존 규칙을 일관되게 따르는 편이 나을 수 있습니다. 특히 루트와 src에 같은 이름의 app 또는 pages 디렉터리를 동시에 두면 루트 디렉터리가 우선되므로, 이전 과정에서 중복 구조를 장기간 남기지 않는 것이 중요합니다. nextjs.org
src를 선택하는 실질적인 기준은 단순합니다. 설정 파일과 제품 코드를 명확히 가르고 싶거나 소스 파일이 많아질 것으로 예상하면 유용합니다. 반대로 학습용 또는 매우 작은 프로젝트에서 루트 구조가 이미 명료하다면 반드시 도입할 이유는 없습니다.
app 폴더에서는 어떤 파일이 실제 경로를 만드나요?
App Router에서 폴더는 URL의 한 조각, 즉 세그먼트를 표현합니다. 하지만 app/dashboard라는 폴더만 만들어서는 /dashboard가 공개 페이지가 되지 않습니다. 그 폴더에 page.tsx가 있으면 페이지 UI를 제공하는 경로가 되고, route.ts가 있으면 Route Handler 기반의 API 엔드포인트가 됩니다. nextjs.org
예를 들어 다음 배치를 생각할 수 있습니다.
src/app/
├─ page.tsx
├─ about/
│ └─ page.tsx
├─ dashboard/
│ ├─ page.tsx
│ └─ reports/
│ └─ page.tsx
└─ api/
└─ users/
└─ route.ts
이 경우 page.tsx는 각각 /, /about, /dashboard, /dashboard/reports에 대응합니다. api/users/route.ts는 UI 페이지가 아니라 API 엔드포인트를 정의하는 파일입니다. page.tsx와 route.ts가 라우트의 공개 진입점을 맡는다는 점을 먼저 이해하면, 나머지 파일을 해당 폴더에 둬도 되는 이유가 분명해집니다. nextjs.org
이 성질을 콜로케이션이라고 볼 수 있습니다. 콜로케이션은 서로 관련된 코드를 가까운 위치에 두는 정리 방식입니다. 예를 들어 dashboard에서만 쓰는 표 컴포넌트, 화면 전용 포맷 함수, 테스트용 데이터 변환 코드를 app/dashboard 근처에 배치할 수 있습니다. 이것이 공용 폴더를 전혀 쓰지 말라는 뜻은 아닙니다. 다른 경로에서도 재사용되는 코드만 더 넓은 범위의 폴더로 이동하면 됩니다.
layout, loading, error 파일은 어떻게 나누나요?
layout.tsx는 공통 UI의 틀을 담당합니다. 루트의 app/layout.tsx는 전체 라우트를 감싸며, 하위 폴더의 layout.tsx는 그 하위 경로에 중첩 적용됩니다. 예를 들어 대시보드의 탐색 메뉴가 /dashboard와 /dashboard/reports에 공통이라면 app/dashboard/layout.tsx에 둘 수 있습니다. nextjs.org
page.tsx는 특정 경로에서 보여 줄 페이지 UI입니다. 레이아웃이 반복되는 외곽 구조라면, 페이지는 그 안에서 경로별로 바뀌는 본문에 가깝습니다. 둘을 분리하면 공통 메뉴나 프레임을 각 페이지에 반복해서 작성하지 않아도 됩니다.
App Router에는 상황별 UI를 위한 예약 파일도 있습니다. loading.tsx는 로딩 UI, error.tsx는 오류 UI, not-found.tsx는 찾을 수 없음 UI를 위해 사용됩니다. 이 파일들은 일반적인 이름의 컴포넌트 파일과 달리 Next.js가 특정 역할로 해석하므로, 역할과 적용 범위를 의식해 배치해야 합니다. nextjs.org
예를 들어 /dashboard 아래에서 데이터 준비 중의 화면을 따로 보여 주고 싶다면 app/dashboard/loading.tsx를, 그 범위의 오류 처리를 위한 화면을 두고 싶다면 app/dashboard/error.tsx를 검토할 수 있습니다. 모든 폴더에 이 파일들을 기계적으로 추가할 필요는 없습니다. 각 경로가 실제로 별도의 대기·오류·미존재 상태 UI를 필요로 하는지에 따라 두는 것이 구조를 간결하게 유지합니다.
동적 경로와 API 경로는 폴더 이름으로 어떻게 표현하나요?
경로 일부가 미리 정해지지 않는 경우에는 대괄호 표기법을 사용합니다. [slug]는 하나의 동적 세그먼트, [...slug]는 뒤따르는 모든 하위 세그먼트, [[...slug]]는 그 하위 세그먼트가 없어도 되는 선택적 형태를 뜻합니다. nextjs.org
가령 글의 식별자가 달라지는 페이지는 다음과 같이 구성할 수 있습니다.
src/app/posts/
└─ [slug]/
└─ page.tsx
이 구조에서 [slug]는 고정된 폴더 이름이 아니라 URL에서 달라지는 부분을 받는 자리입니다. 반면 여러 단계의 경로를 하나의 규칙으로 다뤄야 할 때는 [...slug] 또는 [[...slug]]를 고려합니다. 어떤 표기를 고를지는 ‘반드시 하나 이상의 하위 경로가 있어야 하는가’와 ‘경로가 없는 경우도 같은 화면에서 처리할 것인가’에 따라 달라집니다. nextjs.org
API를 구성할 때는 route.ts가 Route Handler의 진입점입니다. 따라서 app/api/users/route.ts처럼 URL 구조를 폴더로 표현하고 끝에 route.ts를 둘 수 있습니다. 페이지와 API 모두 폴더 계층을 읽으면 대략적인 경로를 추론할 수 있다는 장점이 있지만, 같은 영역에 UI와 서버 처리 코드가 뒤섞여 길어지지 않도록 전용 구현 파일을 적절히 나누는 편이 좋습니다. nextjs.org
라우트 그룹과 비공개 폴더는 왜 필요한가요?
(marketing)처럼 괄호로 감싼 폴더는 라우트 그룹입니다. 이는 URL에는 포함되지 않는 논리적 구분입니다. 예를 들어 회사 소개, 요금 안내처럼 마케팅 성격의 페이지를 한 그룹으로 묶고, 서비스 이용 화면은 별도 구조로 관리할 수 있습니다. app/(marketing)/about/page.tsx는 그룹명 없이 /about 경로가 됩니다. nextjs.org
라우트 그룹은 URL을 바꾸지 않으면서 레이아웃 경계나 코드 소유 범위를 드러내고 싶을 때 유용합니다. 그러나 URL에 이름이 나타나지 않는다는 점 때문에, 서로 다른 그룹에서 결과적으로 동일한 URL을 만들면 충돌합니다. 또한 여러 루트 레이아웃 사이를 이동하는 구성에서는 전체 페이지 로드가 발생할 수 있으므로, 루트 레이아웃을 나누는 설계는 단지 폴더를 예쁘게 정리하기 위한 목적으로 가볍게 적용하지 않는 편이 좋습니다. nextjs.org
_components, _lib처럼 밑줄로 시작하는 폴더는 비공개 폴더로 라우팅에서 제외됩니다. App Router에서는 일반 파일도 기본적으로 라우트가 되지 않기 때문에 밑줄이 반드시 필요한 것은 아닙니다. 그래도 라우팅 특수 파일과 내부 구현의 경계를 눈에 띄게 표시하거나 예약 파일명과의 혼동을 피하고 싶을 때 유용할 수 있습니다. nextjs.org
예를 들어 app/dashboard/_components/summary-card.tsx는 대시보드 전용 UI라는 의도를 보여 줍니다. 하지만 그 컴포넌트가 다른 기능에서도 반복 사용되기 시작했다면, 밑줄 폴더에 계속 두기보다 공용 components 또는 적절한 기능 경계로 옮길지를 검토하는 편이 자연스럽습니다. 폴더 접두사는 접근 제어 장치가 아니라 코드의 역할을 전달하는 표기라는 점을 기억해야 합니다.
components, features, lib, hooks, types는 어떻게 구분하면 좋나요?
이 폴더들은 App Router의 예약 규칙이 아니라 조직화 목적의 선택 사항입니다. 따라서 이름 자체보다 팀이 합의한 책임 경계가 중요합니다. 다음 구분은 자주 쓰이는 출발점입니다.
| 폴더 | 주로 두는 코드 | 배치 판단 기준 |
|---|---|---|
components | 여러 화면에서 재사용하는 UI | 특정 URL이나 도메인에 묶이지 않은가 |
features | 기능 또는 도메인 단위 구현 | 계정, 주문, 대시보드처럼 업무 개념이 뚜렷한가 |
lib | 공통 유틸리티와 클라이언트 | UI가 아닌 공통 도구인가 |
hooks | 재사용하는 훅 | 여러 컴포넌트가 같은 상태·동작을 공유하는가 |
types | 공통 타입 | 여러 영역에서 같은 타입 정의를 참조하는가 |
components에는 범용 버튼뿐 아니라 여러 기능에서 공유하는 복합 UI도 들어갈 수 있습니다. 다만 처음부터 모든 UI를 전역 공용 컴포넌트로 만들면, 실제로는 한 화면에만 필요한 구현까지 추상화될 수 있습니다. 처음에는 라우트 가까이에 두고, 두 곳 이상에서 안정적으로 쓰이며 공통 인터페이스가 분명해질 때 옮기는 방식도 가능합니다.
features는 도메인 중심 구조가 필요한 경우에 특히 읽기 좋습니다. 예를 들어 주문과 계정이 각각 독립적인 화면, UI, 데이터 처리 코드를 가진다면 features/orders, features/account처럼 묶을 수 있습니다. 반면 단순한 사이트에 기능 폴더를 과도하게 만들면 파일을 찾기 위해 여러 폴더를 오가게 될 수 있습니다. 기능 경계가 실제 제품 개념과 맞을 때만 도입하는 편이 낫습니다.
lib는 공통 유틸리티나 서버 클라이언트처럼 화면이 아닌 기반 코드를 두는 후보입니다. 그렇다고 모든 함수 파일을 lib에 쌓아 두면 목적을 알기 어려운 큰 보관함이 될 수 있습니다. 어떤 기능에만 쓰이는 도구라면 그 기능 또는 라우트 가까이에 두고, 여러 곳에서 공유되는 코드만 lib로 올리는 기준이 실용적입니다.
public과 환경 변수 파일은 왜 프로젝트 루트에 두나요?
public은 정적 파일을 두는 프로젝트 루트 폴더입니다. 여기에 넣은 파일은 루트 경로에서 제공되며, 예를 들어 public/profile.png는 /profile.png로 참조합니다. 이미지와 폰트처럼 정적으로 제공할 파일의 위치를 한곳으로 모을 수 있다는 뜻입니다. nextjs.org
src를 사용하더라도 public을 src/public으로 옮기는 구조로 생각하면 안 됩니다. public은 프로젝트 루트에 남고, package.json, next.config.js, tsconfig.json, .env.* 역시 루트에서 관리합니다. 특히 .env.local 같은 로컬 환경 변수 파일은 비밀값을 담을 수 있으므로 버전 관리에 포함하지 않는 운영 원칙을 세우는 것이 중요합니다. nextjs.org
정적 자산과 애플리케이션 소스의 역할을 구분하면 경로를 해석하기 쉬워집니다. src/app 안의 파일은 화면이나 라우팅을 구성하는 코드이고, public 안의 파일은 URL로 참조되는 정적 자산입니다. 같은 이미지를 화면에서 사용하더라도 파일을 어디에 두는지에 따라 제공 방식과 참조 경로를 구별해서 이해해야 합니다.
Pages Router 구조와는 무엇이 다른가요?
Pages Router는 pages 디렉터리 안의 파일이 경로가 되는 방식입니다. 예를 들어 pages/index.tsx는 /, pages/about.tsx는 /about에 대응합니다. _app, _document, 404, 500 등에는 특별한 역할을 가진 예약 파일 규칙도 있습니다. nextjs.org
App Router와 Pages Router를 단순히 폴더 이름만 다른 방식으로 보면 실수하기 쉽습니다. App Router에서는 폴더 세그먼트 아래의 page.tsx, layout.tsx, route.ts 등 특수 파일이 역할을 나누며, 일반 파일은 기본적으로 라우트가 아닙니다. Pages Router에서는 pages 내부 파일이 경로와 더 직접적으로 연결됩니다. nextjs.orgnextjs.org
기존 Pages Router 프로젝트를 다룰 때는 현재의 pages 기반 규칙을 존중해야 합니다. 반대로 새 프로젝트를 시작할 때는 App Router 중심 예시를 기준으로 잡고, 정말 필요한 경우에만 조직화 폴더를 추가하는 접근이 부담을 줄입니다. 두 라우터의 규칙을 같은 디렉터리 설계 안에서 혼동하지 않는 것이 중요합니다.
실제 프로젝트에서는 어떤 기준으로 구조를 선택하나요?
첫째, URL 구조가 먼저입니다. 사용자가 접근할 주요 경로를 적고, 각 경로가 어떤 공통 레이아웃을 공유하는지 확인합니다. 그 결과를 app의 폴더와 page.tsx, layout.tsx 배치로 표현합니다. 라우트 그룹은 URL을 바꾸지 않고 화면 영역이나 레이아웃을 나눌 명확한 이유가 있을 때 사용합니다. nextjs.orgnextjs.org
둘째, 재사용 범위를 판단합니다. 한 라우트에서만 쓰는 코드는 해당 라우트 가까이에 둡니다. 여러 라우트에서 쓰는 UI는 components, 여러 기능에서 쓰는 도구는 lib처럼 더 넓은 범위로 이동할 수 있습니다. 처음부터 공용화하지 말고, 실제 재사용과 변경 패턴이 보일 때 분리하는 편이 불필요한 추상화를 줄이는 데 도움이 됩니다.
셋째, 기능의 독립성을 봅니다. 계정·관리·주문처럼 담당 영역과 용어가 명확한 기능이 커진다면 features 같은 도메인 경계가 유용할 수 있습니다. 반대로 화면 수가 적고 기능 간 구분이 약하면 app의 콜로케이션과 소수의 공용 폴더만으로도 충분할 수 있습니다.
넷째, 팀의 탐색 비용을 점검합니다. 새 구성원이 특정 URL의 코드를 찾을 때 app 경로를 따라가면 페이지와 전용 구현을 발견할 수 있어야 합니다. 공통 버튼이나 유틸리티를 찾을 때도 이름과 위치를 예측할 수 있어야 합니다. 좋은 구조는 폴더 이름의 유행보다 이런 예측 가능성에서 나옵니다.
피해야 할 오해와 구조상의 주의점은 무엇인가요?
첫 번째 오해는 ‘폴더를 만들면 곧 URL이 생긴다’는 생각입니다. App Router에서 폴더는 세그먼트를 나타내지만, 공개 페이지 또는 API 엔드포인트가 되려면 page.tsx 또는 route.ts가 필요합니다. 이 규칙 덕분에 관련 내부 파일을 라우트 폴더에 함께 둘 수 있습니다. nextjs.org
두 번째 오해는 ‘밑줄 폴더가 없으면 내부 파일이 모두 노출된다’는 생각입니다. App Router의 일반 파일은 기본적으로 라우트가 아닙니다. _components 같은 이름은 필수 보안 기능이라기보다 내부 구현이라는 의도를 나타내고 라우팅에서 제외하는 정리 수단입니다. nextjs.org
세 번째 오해는 ‘라우트 그룹 이름도 URL에 들어간다’는 생각입니다. (marketing)의 괄호 이름은 URL에서 제외됩니다. 이 편리함 때문에 오히려 서로 다른 그룹이 같은 최종 URL을 만들지 않도록 확인해야 합니다. 여러 루트 레이아웃을 나누면 그룹 간 이동에서 전체 페이지 로드가 일어날 수 있다는 점도 설계 전에 고려할 항목입니다. nextjs.org
마지막으로, src 도입 과정에서 루트와 src에 같은 app 또는 pages 디렉터리를 병존시키는 실수를 피해야 합니다. 이 경우 루트가 우선되므로, 기대한 소스가 실행되지 않는 것처럼 보일 수 있습니다. 구조 변경은 한 번에 폴더를 늘리는 작업이 아니라, 실제로 어떤 디렉터리가 라우팅의 기준인지 확인하며 진행해야 합니다. nextjs.org
결론: 표준은 폴더 목록보다 역할의 경계입니다
Next.js 프로젝트 구조의 출발점은 app과 특수 파일이 정하는 라우팅 규칙입니다. 새 App Router 프로젝트에서는 src/app을 URL·페이지·레이아웃의 중심으로 두고, public은 루트의 정적 자산 폴더로 유지하며, components, features, lib, hooks, types는 코드의 실제 재사용 범위와 도메인 복잡도에 맞춰 선택할 수 있습니다. nextjs.orgnextjs.org
결국 좋은 구조는 가장 많은 폴더를 가진 구조가 아닙니다. 특정 경로의 화면, 그 화면 전용 코드, 여러 곳에서 공유하는 코드의 위치를 팀원이 쉽게 예측할 수 있는 구조입니다. Next.js의 파일 규칙은 정확히 지키고, 그 위의 조직화 방식은 프로젝트가 성장하는 속도와 변경 패턴에 맞춰 점진적으로 조정하는 것이 균형 잡힌 접근입니다.