Next.js 프로젝트가 커질수록 폴더 구조가 유지보수성을 좌우한다. 파일을 어디에 두느냐에 따라 코드 탐색 속도, 팀원 간 협업 난이도, 빌드 최적화 여부가 달라진다. 아래는 실무에서 자주 쓰이는 구조를 기준으로 각 폴더의 역할을 설명한다.
my-app/
├── app/
│ ├── (auth)/
│ │ ├── login/
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── dashboard/
│ │ └── page.tsx
│ ├── api/
│ │ └── users/
│ │ └── route.ts
│ ├── layout.tsx
│ └── page.tsx
├── components/
│ ├── ui/
│ │ ├── Button.tsx
│ │ └── Input.tsx
│ └── shared/
│ └── Header.tsx
├── lib/
│ ├── db.ts
│ └── utils.ts
├── hooks/
│ └── useAuth.ts
├── types/
│ └── index.ts
└── public/
└── images/
app/ 은 Next.js 13부터 도입된 App Router의 핵심이다. 폴더 이름이 곧 URL 경로가 되며, page.tsx 파일이 있는 폴더만 라우트로 노출된다.
(auth) 처럼 괄호로 감싼 폴더는 Route Group으로, URL에는 나타나지 않지만 공통 layout.tsx를 공유할 때 사용한다. API 엔드포인트는 app/api/ 아래 route.ts 파일로 정의한다.
Route Group: 폴더를 URL 세그먼트로 노출하지 않고 레이아웃만 묶는 App Router 기능.
재사용 가능한 React 컴포넌트를 모아두는 곳이다. 실무에서는 두 가지로 나누는 패턴이 많다.
| 하위 폴더 | 용도 |
|---|---|
ui/ | 버튼, 인풋 등 순수 UI 컴포넌트 (비즈니스 로직 없음) |
shared/ | Header, Footer 등 여러 페이지에서 공유하는 컴포넌트 |
ui/ 컴포넌트는 props만 받고 외부 상태에 의존하지 않아야 테스트와 재사용이 쉽다.
lib/ 에는 특정 컴포넌트에 묶이지 않는 유틸리티와 외부 연동 코드를 둔다.
// lib/db.ts — Prisma 클라이언트 싱글턴
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
DB 연결, 날짜 포맷 함수, 암호화 유틸 등이 lib/의 전형적인 내용이다.
hooks/ 에는 use로 시작하는 커스텀 훅을 모은다. 컴포넌트에서 비즈니스 로직을 분리하는 기본 수단이다.
// hooks/useAuth.ts
import { useSession } from "next-auth/react";
export function useAuth() {
const { data: session, status } = useSession();
return { user: session?.user, isLoading: status === "loading" };
}
types/ 는 프로젝트 전역에서 공유하는 TypeScript 타입 정의를 담는다. 페이지나 API Route마다 같은 타입을 중복 선언하는 것을 막는다.
커스텀 훅(Custom Hook): React 훅을 조합해 만든 재사용 가능한 상태·로직 단위.
정적 파일(이미지, 폰트, robots.txt 등)을 두는 곳이다. public/images/logo.png는 코드에서 /images/logo.png로 참조된다. Next.js가 빌드 시 이 경로를 그대로 서빙하므로 별도 import 없이 사용 가능하다.
app/ 경로 아래에 함께 두어 응집도를 높인다.components/, lib/, hooks/로 올린다.components/ui/, components/shared/ 처럼 기능 단위로 하위 분리한다.