프로젝트 구조와 구성
프로젝트 구조와 구성 (Project Structure and Organization)
Next.js에는 폴더와 파일이 라우트와 특별한 역할을 갖게 하는 컨벤션(convention) 이 있어요. 이 글에서는 App Router 기준 Next.js의 폴더·파일 컨벤션 전체를 훑어보고, 프로젝트를 어떻게 구성하면 좋은지 권장 사항을 살펴볼게요.
폴더·파일 컨벤션
최상위 폴더
최상위 폴더는 애플리케이션 코드와 정적 자산을 정리하는 데 쓰여요.
| 폴더 | 역할 |
|---|---|
app |
App Router |
pages |
Pages Router |
public |
서빙할 정적 자산 |
src |
선택적인 애플리케이션 소스 폴더 |
최상위 파일
최상위 파일은 앱 설정, 의존성 관리, 프록시 실행, 모니터링 통합, 환경 변수 정의 등에 쓰여요.
| 파일 | 역할 |
|---|---|
next.config.js |
Next.js 설정 파일 |
package.json |
프로젝트 의존성과 스크립트 |
instrumentation.ts |
OpenTelemetry·인스트루멘테이션 파일 |
proxy.ts |
Next.js 요청 프록시 |
.env |
환경 변수 (버전 관리에 포함하면 안 됨) |
.env.local |
로컬 환경 변수 (버전 관리에 포함하면 안 됨) |
.env.production |
프로덕션 환경 변수 (버전 관리에 포함하면 안 됨) |
.env.development |
개발 환경 변수 (버전 관리에 포함하면 안 됨) |
eslint.config.mjs |
ESLint 설정 파일 |
.gitignore |
Git에서 무시할 파일·폴더 |
next-env.d.ts |
Next.js용 TypeScript 선언 파일 (버전 관리 제외) |
tsconfig.json |
TypeScript 설정 파일 |
jsconfig.json |
JavaScript 설정 파일 |
라우팅 파일 (Routing Files)
라우트를 노출하려면 page, 공유 UI(헤더·내비·푸터)에는 layout, 스켈레톤에는 loading, 에러 경계에는 error, API에는 route 파일을 사용해요.
| 파일 | 확장자 | 역할 |
|---|---|---|
layout |
.js .jsx .tsx |
Layout |
page |
.js .jsx .tsx |
Page |
loading |
.js .jsx .tsx |
Loading UI |
not-found |
.js .jsx .tsx |
Not found UI |
error |
.js .jsx .tsx |
Error UI |
global-error |
.js .jsx .tsx |
Global error UI |
route |
.js .ts |
API 엔드포인트 |
template |
.js .jsx .tsx |
다시 렌더링되는 레이아웃 |
default |
.js .jsx .tsx |
병렬 라우트 폴백 페이지 |
중첩 라우트 (Nested Routes)
폴더가 URL 세그먼트를 정의하고, 폴더를 중첩하면 세그먼트도 중첩돼요. 어느 레벨이든 레이아웃이 하위 세그먼트를 감싸고, page나 route 파일이 있으면 라우트가 공개돼요.
| 경로 | URL 패턴 | 비고 |
|---|---|---|
app/layout.tsx |
— | 루트 레이아웃이 모든 라우트를 감쌈 |
app/blog/layout.tsx |
— | /blog와 하위를 감쌈 |
app/page.tsx |
/ |
공개 라우트 |
app/blog/page.tsx |
/blog |
공개 라우트 |
app/blog/authors/page.tsx |
/blog/authors |
공개 라우트 |
동적 라우트 (Dynamic Routes)
세그먼트를 대괄호로 감싸 파라미터화해요. 단일 파라미터는 [segment], catch-all은 [...segment], 선택적 catch-all은 [[...segment]]를 써요. 값은 params prop으로 접근해요.
| 경로 | URL 패턴 |
|---|---|
app/blog/[slug]/page.tsx |
/blog/my-first-post |
app/shop/[...slug]/page.tsx |
/shop/clothing, /shop/clothing/shirts |
app/docs/[[...slug]]/page.tsx |
/docs, /docs/layouts-and-pages, /docs/api-reference/use-router |
라우트 그룹과 프라이빗 폴더
라우트 그룹 (group)으로 URL을 바꾸지 않고 코드를 정리하고, 프라이빗 폴더 _folder로 라우팅되지 않는 파일을 함께 둘 수 있어요.
| 경로 | URL 패턴 | 비고 |
|---|---|---|
app/(marketing)/page.tsx |
/ |
그룹은 URL에서 생략됨 |
app/(shop)/cart/page.tsx |
/cart |
(shop) 안에서 레이아웃 공유 |
app/blog/_components/Post.tsx |
— | 라우팅되지 않음 — UI 유틸의 안전한 자리 |
app/blog/_lib/data.ts |
— | 라우팅되지 않음 — 유틸의 안전한 자리 |
프로젝트 구성하기
Next.js는 프로젝트 파일을 어떻게 구성하고 콜로케이션(colocate)할지에 대해 의견이 없는(unopinionated) 편이지만, 구성을 돕는 여러 기능을 제공해요.
컴포넌트 계층 (Component Hierarchy)
특별 파일에 정의된 컴포넌트는 일정한 계층으로 렌더링돼요.
layout.jstemplate.jserror.js(React 에러 경계)loading.js(React suspense 경계)not-found.js("not found" UI용 React 에러 경계)page.js또는 중첩된layout.js
컴포넌트는 중첩 라우트에서 재귀적으로 렌더링돼요. 즉, 한 라우트 세그먼트의 컴포넌트는 부모 세그먼트의 컴포넌트 안에 중첩돼요.
콜로케이션 (Colocation)
app 폴더에서 중첩 폴더는 라우트 구조를 정의해요. 각 폴더는 URL 경로의 해당 세그먼트에 매핑되는 라우트 세그먼트예요.
다만 라우트 구조가 폴더로 정의돼도, 라우트 세그먼트에 page.js나 route.js 파일을 추가하기 전까지 라우트는 공개되지 않아요. 게다가 라우트가 공개돼도 page.js/route.js가 반환하는 콘텐츠만 클라이언트로 보내져요.
즉, 프로젝트 파일을 app 폴더 안의 라우트 세그먼트에 안전하게 콜로케이션해도 실수로 라우팅되지 않아요. 물론 app 안에 파일을 콜로케이션할 수는 있지만 반드시 그럴 필요는 없어요. 원하면 app 폴더 밖에 둘 수도 있어요.
프라이빗 폴더 (Private Folders)
폴더 이름 앞에 밑줄을 붙이면(_folderName) 프라이빗 폴더가 돼요. 이것은 그 폴더가 프라이빗 구현 세부 사항이고 라우팅 시스템이 고려하지 않아야 함을 뜻하며, 그 폴더와 모든 하위 폴더를 라우팅에서 제외시켜요.
app 폴더의 파일은 기본적으로 안전하게 콜로케이션되므로, 프라이빗 폴더가 콜로케이션에 필수는 아니에요. 하지만 UI 로직과 라우팅 로직 분리, 프로젝트·Next.js 생태계 전반의 내부 파일 일관된 구성, 코드 편집기에서 정렬·그룹화, 미래의 Next.js 파일 컨벤션과의 이름 충돌 방지에 유용해요.
참고로 URL이 밑줄로 시작하는 세그먼트를 만들고 싶으면 %5F(밑줄의 URL 인코딩)로 시작하는 폴더명(%5FfolderName)을 쓰면 돼요.
라우트 그룹 (Route Groups)
폴더를 괄호로 감싸면((folderName)) 라우트 그룹이 돼요. 이것은 그 폴더가 조직 목적이며 라우트 URL 경로에 포함되지 않아야 함을 뜻해요.
라우트 그룹은 이런 곳에 유용해요.
- 사이트 섹션·의도·팀별로 라우트 정리 (예: 마케팅 페이지, 관리자 페이지)
- 같은 라우트 세그먼트 레벨에서 중첩 레이아웃 활성화:
- 같은 세그먼트에서 여러 중첩 레이아웃(여러 루트 레이아웃 포함) 만들기
- 공통 세그먼트의 일부 라우트에만 레이아웃 적용하기
src 폴더
Next.js는 애플리케이션 코드(app 포함)를 선택적인 src 폴더에 저장하는 걸 지원해요. 이렇게 하면 대부분 프로젝트 루트에 있는 설정 파일과 애플리케이션 코드를 분리할 수 있어요.
조직 전략 예시
공통 전략 몇 가지를 간단히 소개할게요. 결론은 팀과 자신에게 맞는 전략을 고르고 프로젝트 전반에 일관되게 쓰라는 거예요. 아래 예시에서 components·lib 폴더는 일반화된 자리 표시자일 뿐, 그 이름이 특별한 프레임워크 의미를 갖는 건 아니에요. 프로젝트에 따라 ui, utils, hooks, styles 같은 폴더를 써도 무방해요.
app밖에 프로젝트 파일 두기 — 모든 앱 코드를 프로젝트 루트의 공유 폴더에 두고,app폴더는 라우팅 전용으로 유지.app안의 최상위 폴더에 두기 — 모든 앱 코드를app폴더 루트의 공유 폴더에 저장.- 기능·라우트별로 분리하기 — 전역 공유 코드는 루트
app에 두고, 더 특화된 코드는 그것을 쓰는 라우트 세그먼트로 분리. - URL 경로를 바꾸지 않고 라우트 정리하기 — 괄호의 폴더(예:
(marketing),(shop))는 URL에서 생략돼요. 그룹 안에layout.js를 추가하면 같은 URL 계층을 공유하면서도 그룹마다 다른 레이아웃을 만들 수 있어요. - 특정 세그먼트에 레이아웃 적용하기 — 같은 레이아웃을 공유할 라우트를 새 라우트 그룹(예:
(shop))으로 묶고, 그룹 밖의 라우트(예:checkout)는 레이아웃을 공유하지 않게 해요. - 특정 라우트에 로딩 스켈레톤 적용하기 — 새 라우트 그룹(예:
/(overview))을 만들고loading.tsx를 그 안으로 옮기면, URL 구조를 바꾸지 않고 해당 라우트에만loading.js가 적용돼요. - 여러 루트 레이아웃 만들기 — 최상위
layout.js를 제거하고 각 라우트 그룹 안에layout.js를 추가하면, 완전히 다른 UI·경험을 갖는 섹션으로 앱을 분할할 수 있어요. 각 루트 레이아웃에<html>·<body>태그를 넣어야 해요.
더 알아보기 (Learn more)
- Routing Files — App Router의 특별 파일 컨벤션 전체
- Route Groups — 라우트 그룹 컨벤션
- Dynamic Routes — 동적 라우트 세그먼트
- Parallel and Intercepted Routes — 슬롯 기반 레이아웃과 모달 라우팅