프로젝트 구조
src/app·public·설정 파일·빌드 산출물의 책임을 구분하고 폴더와 page.tsx가 URL을 만드는 규칙을 익힙니다.
Next.js 16 프로젝트를 만들어 실행했으니, 이제 프로젝트 내부의 구조를 살펴봅니다. 각 디렉터리와 파일의 역할을 알면 라우트와 컴포넌트, 설정 파일이 맡는 일을 구분하기 쉽습니다. 앞에서 create-next-app으로 프로젝트를 만들 때 src/ 디렉터리를 쓰기로 했으므로, src 폴더를 기준으로 설명합니다.
주요 디렉터리와 파일
Next.js 프로젝트의 핵심은 src 디렉터리 안의 app 디렉터리이며, 그 밖에도 몇 가지 중요한 최상위 디렉터리와 파일이 있습니다. 아래에서는 코드를 직접 작성하는 src/와 라우팅을 정하는 src/app/을 먼저 보고, 정적 파일을 두는 public/, 도구가 만들어 관리하는 node_modules/와 .next/를 차례로 봅니다. 앞의 세 곳은 직접 파일을 더하거나 고치는 곳이고, 뒤의 두 곳은 명령을 실행하면 생기는 결과이므로 손으로 고치지 않는다는 점이 다릅니다.
src/ 디렉터리
프로젝트의 소스 코드가 놓이는 곳입니다. 이 교재는 애플리케이션의 소스를 src/ 아래에 모으며, src/는 선택해서 쓰는 구성이고 public/과 package.json, next.config.ts 같은 설정 파일은 프로젝트 루트에 둡니다.
뒤의 예시는 프로젝트 루트에 별도의 app/이 없다고 전제합니다. 루트에도 app/을 만들어 두면 src/app/은 무시되므로, 같은 page.tsx를 두 곳에 복사하기보다 실제로 쓰는 라우트 트리를 하나로 정하고 고칠 위치를 확인합니다(Next.js 문서의 src 폴더).
src/app/ 디렉터리 (App Router의 핵심)
이 디렉터리가 Next.js 16의 App Router가 작동하는 방식의 핵심이며, app 안의 파일과 폴더의 구조가 애플리케이션의 라우팅을 정의합니다.
src/app/page.tsx(또는 .js, .jsx)는 애플리케이션의 루트 페이지로, http://localhost:3000/에 들어왔을 때 가장 먼저 보이는 페이지입니다. 각 라우트 세그먼트(폴더) 안에 page.tsx가 있으면 그 라우트의 UI가 렌더링되며, 이 파일은 기본적으로 서버 컴포넌트(Server Component)로 동작합니다.
src/app/layout.tsx(또는 .js, .jsx)는 애플리케이션의 공통 레이아웃으로, <html>과 <body> 태그처럼 모든 페이지가 함께 쓰는 UI를 정의합니다. 이 교재처럼 라우트 트리가 하나라면 src/app/layout.tsx가 루트 레이아웃(Root Layout)이 되어 <html>과 <body>를 담으며, 라우트 그룹 같은 방법으로 여러 루트 트리를 만들 때는 각 트리의 최상위 layout.tsx가 이 역할을 맡을 수 있습니다. create-next-app이 만든 이 파일의 <html>에는 lang="en"이 들어 있으며, 이 교재는 3장 1절의 루트 레이아웃 예제에서 lang="ko"로 바꿉니다. layout.tsx도 기본적으로 서버 컴포넌트입니다. src/app/globals.css는 애플리케이션 전체에 적용되는 전역 스타일을 정의하는 CSS 파일이며, layout.tsx에서 가져와 씁니다.
app 안에 새 폴더를 만들면 그 폴더의 이름이 URL 경로의 세그먼트가 되므로, 예를 들어 src/app/dashboard/page.tsx는 /dashboard 경로에 연결됩니다. 폴더 안에 page.tsx가 없으면 그 경로는 페이지가 아닙니다.
필요할 때 더하는 특수 파일도 있습니다. loading.tsx는 그 라우트 세그먼트의 내용을 불러오는 동안 보여 줄 UI를 정의하며 React의 Suspense와 함께 동작하고, error.tsx는 그 세그먼트에서 오류가 났을 때 보여 줄 UI를 정의하며 React의 에러 바운더리처럼 동작합니다. not-found.tsx는 그 라우트에서 내용을 찾을 수 없을 때 보여 줄 UI, 곧 404 페이지를 정의합니다.
public/ 디렉터리
이미지와 폰트, favicon.ico처럼 웹 서버가 그대로 제공해야 하는 정적 파일을 두는 곳입니다. 이 디렉터리 안의 파일은 애플리케이션의 루트 경로에서 바로 접근할 수 있으므로, 예를 들어 public/logo.png는 /logo.png로 접근합니다.
node_modules/ 디렉터리
npm install이나 yarn add로 설치한 모든 Node.js 패키지가 저장되는 곳입니다. 용량이 아주 크므로 Git 같은 버전 관리 시스템에는 넣지 않는 것이 일반적이며, .gitignore에 이미 설정되어 있습니다.
.next/ 디렉터리
Next.js의 개발 서버와 빌드가 만드는 결과와 캐시가 저장되는 곳입니다. 이 내용은 Next.js가 관리하므로 개발자가 직접 고칠 필요가 없습니다.
더 깊이: .next/ 안에서 개발 결과와 빌드 결과가 나뉘는 방식
Next.js 16부터 next dev와 next build는 서로 다른 출력 디렉터리를 씁니다. 개발 서버는 결과를 .next/dev에 쓰고 빌드는 그 밖의 .next 아래에 쓰므로, 개발 서버를 켜 둔 채 다른 터미널에서 next build를 실행해도 서로의 결과를 덮어쓰지 않습니다. 대신 같은 프로젝트에서 next dev를 두 개 띄우거나 next build를 동시에 두 번 실행하는 것은 잠금 파일(lockfile) 장치가 막습니다. 자세한 변경 내용은 Next.js 16 업그레이드 안내에 정리되어 있습니다.
이 폴더에는 다음 실행을 빠르게 하려고 Turbopack이 남기는 파일 시스템 캐시도 들어 있습니다. 개발용 캐시는 .next/dev/cache/turbopack에, 빌드용 캐시는 .next/cache/turbopack에 저장되며, 이 캐시가 기본으로 켜진 것은 개발 서버가 16.1, 빌드가 16.3부터입니다. 그래서 .next를 지우면 이 캐시도 함께 사라져 다음 실행은 처음부터 다시 컴파일합니다. 빌드할 때마다 깨끗한 환경에서 시작하는 CI나 컨테이너에서는 .next/cache를 따로 보존하거나 복원해야 빌드 캐시의 효과를 볼 수 있습니다.
주요 설정 파일
프로젝트의 최상위 디렉터리에는 Next.js 애플리케이션의 동작을 정하는 몇 가지 중요한 설정 파일이 있습니다.
package.json은 프로젝트의 이름과 버전, 설명 같은 정보를 정의하며, dependencies에는 실행에 필요한 라이브러리를, devDependencies에는 개발할 때만 필요한 라이브러리를 적고, scripts에는 npm run dev, npm run build, npm run start처럼 자주 쓰는 명령을 정의합니다. next.config.ts는 Next.js의 설정을 바꾸는 파일로, 이미지 최적화와 번들러 설정 같은 기본 동작을 조정합니다. Next.js 16은 Turbopack이 기본이므로 webpack 설정은 --webpack으로 Webpack을 고른 실행에만 적용됩니다. 다만 webpack 설정이 있는 채로 기본 next build를 실행하면 잘못된 구성을 막으려고 빌드가 실패하므로, 그때는 --webpack으로 빌드하거나 설정을 Turbopack 옵션으로 옮깁니다.
TypeScript를 쓴다면 tsconfig.json이 TypeScript 컴파일러의 설정, 곧 어떤 버전의 JavaScript로 컴파일하고 어떤 모듈 시스템을 쓸지 등을 정의합니다. ESLint를 쓴다면 eslint.config.mjs가 코드 스타일과 정적 분석 규칙을 정의해 코드를 일관되게 유지하고 잠재적인 오류를 미리 찾게 돕습니다. .gitignore는 Git이 추적하지 않을 파일과 디렉터리를 정하는 파일로, node_modules/와 .next/처럼 필요 없거나 용량이 큰 폴더가 이미 들어 있습니다.
App Router의 라우팅 기본 원리
일반 폴더는 URL의 세그먼트를 만들고, 그 위치의 page.tsx가 페이지의 UI를 공개합니다. 자기 page.tsx가 없는 상위 폴더도 하위 경로의 세그먼트가 될 수 있습니다. 응답만 돌려주는 엔드포인트는 route.ts로 정의하며, 라우트 그룹과 비공개 폴더(private folder) 같은 예외는 다음 장에서 살펴봅니다.
예를 들어 루트 라우트는 src/app/page.tsx, 중첩 라우트는 src/app/dashboard/page.tsx로 /dashboard, 동적 라우트는 src/app/products/[id]/page.tsx로 /products/123이 되며, 여기서 [id]는 바뀌는 값입니다. 이런 구조 덕분에 Next.js는 라우트 세그먼트마다 독립된 레이아웃과 로딩 UI, 오류 UI를 정의할 수 있습니다.
NEXT · FILE TO URL
파일 위치와 공개 URL의 대응
파일의 포함 경로를 보고 페이지 진입점·정적 파일·미공개 소스를 구분합니다. 화살표는 네 가지 독립 대응입니다.
상위 폴더 자체에 page.tsx가 없어도 하위 페이지의 URL에는 그 세그먼트가 포함됩니다. 일반 소스 파일명은 자동 공개 경로가 아닙니다.
위 그림은 파일의 위치와 공개되는 URL의 대응을 보여 줍니다. 그림의 요약처럼 파일이 들어 있는 경로를 보고 페이지의 진입점과 정적 파일, 공개되지 않는 소스를 구분하며, 각 화살표는 차례로 실행되는 단계가 아니라 네 가지 독립된 대응입니다. src/app의 page.tsx는 루트 페이지 /가 되고, src/app/catalog의 page.tsx는 중첩 페이지 /catalog가 되며, 같은 폴더의 utils.ts는 일반 소스 파일이라 자동으로 공개되는 URL이 없고, public/icons의 star.svg는 public 접두사를 뺀 /icons/star.svg로 공개됩니다. 그림의 마지막 설명처럼 상위 폴더 자체에 page.tsx가 없어도 하위 페이지의 URL에는 그 세그먼트가 들어가며, 일반 소스의 파일 이름은 자동으로 공개되는 경로가 아닙니다.
루트의 별도 app/이나 사용자가 정의한 재작성(rewrite)이 없는 프로젝트에서 이 대응을 표로 정리하면 다음과 같습니다. URL을 만들 때 src/app과 마지막의 page.tsx는 주소에 넣지 않고, 정적 파일은 public 접두사를 뺀 그 안의 경로를 씁니다.
| 실제 파일 | 요청 URL 또는 공개 여부 | 이유 |
|---|---|---|
src/app/page.tsx | / | 루트 페이지가 반환하는 UI |
src/app/catalog/page.tsx | /catalog | catalog 세그먼트의 페이지 |
src/app/catalog/utils.ts | 공개되는 URL 없음 | 일반 소스 파일은 페이지의 진입점이 아님 |
public/icons/star.svg | /icons/star.svg | public 안의 경로에 있는 정적 파일 |
/catalog/utils.ts로 요청해도 이 TypeScript 소스가 저절로 내려오지는 않습니다. 반대로 공개할 정적 파일을 public에 넣으면 그 경로로 제공된다는 차이를 기억하세요(Next.js 문서의 프로젝트 구조와 파일 배치).
정리
- 이 교재의 라우트 트리는
src/app/이며, 루트에app/이 함께 있으면src/app/은 무시됩니다. page.tsx는 그 세그먼트의 페이지를,layout.tsx는 공통 UI를,loading.tsx·error.tsx·not-found.tsx는 로딩·오류·찾을 수 없음 UI를 정의하며, 기본은 서버 컴포넌트입니다.- 폴더 이름이 URL의 세그먼트가 되지만, 그 폴더에
page.tsx가 있어야 그 경로가 페이지가 됩니다. public/의 파일은public을 뺀 루트 경로로 공개되고,app안의 일반 소스 파일은 자동으로 공개되지 않습니다.node_modules/와.next/는 도구가 관리하는 결과이고,package.json,next.config.ts,tsconfig.json,eslint.config.mjs가 프로젝트의 설정을 맡습니다.
연습 문제
1. 경로를 바꾸어 판단하기
src/app/reports/2026/page.tsx, src/app/reports/loading.tsx, public/docs/guide.pdf만 더했고, reports 자체의 page.tsx와 route.ts는 없습니다. 새 페이지와 PDF의 URL을 쓰고, /reports가 독립된 페이지가 되는지 설명해 보세요.
해설 보기
페이지는 /reports/2026, PDF는 /docs/guide.pdf입니다. 상위의 reports는 자식 URL에 들어가지만, 그 위치에 페이지의 진입점이 없으므로 이 조건에서 /reports 자체는 페이지가 아닙니다. loading.tsx는 로딩 UI를 돕는 특수 파일일 뿐 혼자서 페이지 URL을 만들지 않습니다.
2. 두 곳에 있는 app 디렉터리
프로젝트 루트에 실수로 app/page.tsx를 만들었는데, 교재대로 src/app/page.tsx를 고쳐도 화면이 바뀌지 않습니다. 왜 그럴까요?
해설 보기
루트에 app/이 있으면 Next.js는 그것을 라우트 트리로 쓰고 src/app/은 무시합니다. 그래서 src/app/page.tsx를 고쳐도 실제로 렌더링되는 것은 루트의 app/page.tsx입니다.
라우트 트리를 하나로 정하고, 쓰지 않는 쪽의 app/ 디렉터리를 지우거나 옮긴 뒤 개발 서버의 화면을 다시 확인합니다.
3. 공개되는 파일과 공개되지 않는 파일
src/app/settings/config.json과 public/config.json이 있습니다. 브라우저에서 /settings/config.json과 /config.json을 요청하면 각각 어떻게 될까요?
해설 보기
/config.json은 public/config.json이 루트 경로로 공개되므로 그 파일의 내용이 내려옵니다. /settings/config.json은 src/app/settings 안의 일반 파일이 자동으로 공개되지 않으므로 그 내용이 내려오지 않고, 맞는 라우트가 없다면 찾을 수 없음 응답을 받습니다.
그래서 브라우저에 공개해도 되는 파일만 public에 두고, 공개하면 안 되는 설정은 app 안의 소스나 서버 쪽 환경 변수로 관리합니다.
다음 글 개발 서버 실행 및 기본 설정에서는 개발 서버가 코드를 감시하고 오류를 보여 주는 방식을 확인하고, 포트와 이미지, 엄격 모드, 환경 변수의 기본 설정을 조정합니다.