[Next.js] Next.js 15 App Router 표준 폴더 구조 설계




확장 가능하고 유지보수가 용이한 Next.js 15 애플리케이션을 구축하기 위해서는 체계적인 폴더 구조가 필수입니다. 이 가이드에서는 소규모부터 대규모 프로젝트까지 모든 상황에 적용할 수 있는 표준 폴더 구조와 설계 원칙을 제시합니다.


기본 App Router 구조

핵심 폴더와 파일

Next.js 15 App Router의 기본 구조를 이해하는 것부터 시작합니다:

project-root/
├── app/                        # App Router 루트 디렉토리
│   ├── globals.css            # 전역 스타일
│   ├── layout.tsx             # 루트 레이아웃
│   ├── page.tsx               # 홈페이지 (/)
│   ├── loading.tsx            # 전역 로딩 UI
│   ├── error.tsx              # 전역 에러 UI
│   ├── not-found.tsx          # 404 페이지
│   └── template.tsx           # 루트 템플릿 (선택적)
├── public/                    # 정적 파일들
├── next.config.js             # Next.js 설정
├── package.json
├── tailwind.config.ts         # Tailwind CSS 설정
├── tsconfig.json              # TypeScript 설정
└── .env.local                 # 환경 변수

특수 파일 역할

App Router에서 특별한 의미를 가지는 파일들입니다:

  • layout.tsx: 페이지들 간 공유되는 UI 레이아웃
  • page.tsx: 라우트의 실제 페이지 컴포넌트
  • loading.tsx: 로딩 상태 UI
  • error.tsx: 에러 상태 UI
  • not-found.tsx: 404 에러 페이지
  • template.tsx: 레이아웃과 유사하지만 매번 새 인스턴스 생성
  • route.tsx: API 라우트 핸들러

소규모 프로젝트 구조

기본 구조 (1-5페이지)

간단한 랜딩 페이지나 포트폴리오 사이트에 적합한 구조입니다:

my-portfolio/
├── app/
│   ├── globals.css
│   ├── layout.tsx
│   ├── page.tsx                 # 홈페이지
│   ├── about/
│   │   └── page.tsx             # 소개 페이지
│   ├── projects/
│   │   ├── page.tsx             # 프로젝트 목록
│   │   └── [id]/
│   │       └── page.tsx         # 프로젝트 상세
│   ├── contact/
│   │   └── page.tsx             # 연락처
│   └── api/
│       └── contact/
│           └── route.ts         # 연락 폼 API
├── components/
│   ├── ui/
│   │   ├── Button.tsx
│   │   ├── Card.tsx
│   │   └── Input.tsx
│   ├── Header.tsx
│   ├── Footer.tsx
│   └── ContactForm.tsx
├── lib/
│   ├── utils.ts
│   └── validations.ts
├── types/
│   └── index.ts
├── data/
│   ├── projects.ts
│   └── skills.ts
└── public/
    ├── images/
    └── icons/

소규모 프로젝트 구성 예시

// app/layout.tsx
import './globals.css'
import { Inter } from 'next/font/google'
import Header from '@/components/Header'
import Footer from '@/components/Footer'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: '홍길동 포트폴리오',
  description: '프론트엔드 개발자 홍길동의 포트폴리오입니다.'
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="ko">
      <body className={inter.className}>
        <Header />
        <main className="min-h-screen">
          {children}
        </main>
        <Footer />
      </body>
    </html>
  )
}
// components/Header.tsx
import Link from 'next/link'

const navigation = [
  { name: '홈', href: '/' },
  { name: '소개', href: '/about' },
  { name: '프로젝트', href: '/projects' },
  { name: '연락처', href: '/contact' }
]

export default function Header() {
  return (
    <header className="bg-white shadow-sm">
      <nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="flex justify-between h-16">
          <div className="flex">
            <Link href="/" className="flex items-center">
              <span className="text-xl font-bold">홍길동</span>
            </Link>
          </div>
          
          <div className="flex space-x-8">
            {navigation.map((item) => (
              <Link
                key={item.name}
                href={item.href}
                className="inline-flex items-center text-gray-500 hover:text-gray-700"
              >
                {item.name}
              </Link>
            ))}
          </div>
        </div>
      </nav>
    </header>
  )
}

중규모 프로젝트 구조

확장된 구조 (10-50페이지)

블로그, 소규모 전자상거래, SaaS 애플리케이션에 적합한 구조입니다:

my-saas-app/
├── app/
│   ├── globals.css
│   ├── layout.tsx
│   ├── page.tsx                 # 랜딩 페이지
│   ├── loading.tsx
│   ├── error.tsx
│   ├── not-found.tsx
│   ├── (marketing)/            # 라우트 그룹
│   │   ├── about/
│   │   │   └── page.tsx
│   │   ├── pricing/
│   │   │   └── page.tsx
│   │   ├── blog/
│   │   │   ├── page.tsx
│   │   │   ├── [slug]/
│   │   │   │   └── page.tsx
│   │   │   └── category/
│   │   │       └── [category]/
│   │   │           └── page.tsx
│   │   └── contact/
│   │       └── page.tsx
│   ├── (auth)/                 # 인증 관련
│   │   ├── login/
│   │   │   └── page.tsx
│   │   ├── signup/
│   │   │   └── page.tsx
│   │   └── forgot-password/
│   │       └── page.tsx
│   ├── dashboard/              # 대시보드
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   ├── loading.tsx
│   │   ├── projects/
│   │   │   ├── page.tsx
│   │   │   ├── [id]/
│   │   │   │   └── page.tsx
│   │   │   └── new/
│   │   │       └── page.tsx
│   │   ├── settings/
│   │   │   ├── page.tsx
│   │   │   ├── profile/
│   │   │   │   └── page.tsx
│   │   │   └── billing/
│   │   │       └── page.tsx
│   │   └── analytics/
│   │       └── page.tsx
│   └── api/                    # API 라우트
│       ├── auth/
│       │   ├── login/
│       │   │   └── route.ts
│       │   └── register/
│       │       └── route.ts
│       ├── projects/
│       │   ├── route.ts
│       │   └── [id]/
│       │       └── route.ts
│       ├── users/
│       │   └── route.ts
│       └── webhooks/
│           └── stripe/
│               └── route.ts
├── components/
│   ├── ui/                     # 기본 UI 컴포넌트
│   │   ├── button/
│   │   │   ├── index.tsx
│   │   │   └── button.stories.tsx
│   │   ├── input/
│   │   │   ├── index.tsx
│   │   │   └── input.stories.tsx
│   │   ├── modal/
│   │   └── toast/
│   ├── forms/                  # 폼 컴포넌트
│   │   ├── LoginForm.tsx
│   │   ├── ProjectForm.tsx
│   │   └── ContactForm.tsx
│   ├── layout/                 # 레이아웃 컴포넌트
│   │   ├── Header.tsx
│   │   ├── Footer.tsx
│   │   ├── Sidebar.tsx
│   │   └── DashboardLayout.tsx
│   ├── marketing/              # 마케팅 페이지 컴포넌트
│   │   ├── Hero.tsx
│   │   ├── Features.tsx
│   │   ├── Testimonials.tsx
│   │   └── CTA.tsx
│   └── dashboard/              # 대시보드 컴포넌트
│       ├── ProjectCard.tsx
│       ├── StatsCard.tsx
│       └── Charts/
│           ├── LineChart.tsx
│           └── BarChart.tsx
├── lib/                        # 유틸리티 라이브러리
│   ├── auth.ts
│   ├── database.ts
│   ├── stripe.ts
│   ├── email.ts
│   ├── utils.ts
│   ├── validations.ts
│   └── constants.ts
├── hooks/                      # 커스텀 훅
│   ├── useAuth.ts
│   ├── useLocalStorage.ts
│   └── useDebounce.ts
├── types/                      # TypeScript 타입 정의
│   ├── auth.ts
│   ├── project.ts
│   ├── user.ts
│   └── api.ts
├── styles/                     # 스타일 파일
│   ├── components.css
│   └── utilities.css
├── data/                       # 정적 데이터
│   ├── plans.ts
│   ├── features.ts
│   └── testimonials.ts
├── config/                     # 설정 파일
│   ├── database.ts
│   ├── auth.ts
│   └── site.ts
└── public/
    ├── images/
    ├── icons/
    └── docs/

중규모 프로젝트 구성 예시

// app/(marketing)/layout.tsx
import MarketingHeader from '@/components/layout/MarketingHeader'
import MarketingFooter from '@/components/layout/MarketingFooter'

export default function MarketingLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <>
      <MarketingHeader />
      {children}
      <MarketingFooter />
    </>
  )
}
// app/dashboard/layout.tsx
import { redirect } from 'next/navigation'
import { getServerSession } from 'next-auth'
import DashboardSidebar from '@/components/layout/DashboardSidebar'
import DashboardHeader from '@/components/layout/DashboardHeader'
import { authOptions } from '@/lib/auth'

export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const session = await getServerSession(authOptions)

  if (!session) {
    redirect('/login')
  }

  return (
    <div className="flex h-screen bg-gray-100">
      <DashboardSidebar />
      <div className="flex-1 flex flex-col overflow-hidden">
        <DashboardHeader user={session.user} />
        <main className="flex-1 overflow-x-hidden overflow-y-auto bg-gray-50">
          {children}
        </main>
      </div>
    </div>
  )
}
// lib/auth.ts
import { NextAuthOptions } from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
import { PrismaAdapter } from '@next-auth/prisma-adapter'
import { prisma } from '@/lib/database'

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    session: async ({ session, token }) => {
      if (session?.user) {
        session.user.id = token.sub!
      }
      return session
    },
    jwt: async ({ user, token }) => {
      if (user) {
        token.sub = user.id
      }
      return token
    },
  },
  session: {
    strategy: 'jwt',
  },
  pages: {
    signIn: '/login',
    error: '/auth/error',
  },
}

대규모 프로젝트 구조

엔터프라이즈급 구조 (100+ 페이지)

대규모 팀과 복잡한 비즈니스 로직을 가진 애플리케이션에 적합한 구조입니다:

enterprise-app/
├── app/
│   ├── globals.css
│   ├── layout.tsx
│   ├── page.tsx
│   ├── template.tsx
│   ├── loading.tsx
│   ├── error.tsx
│   ├── global-error.tsx
│   ├── not-found.tsx
│   ├── (public)/              # 공개 영역
│   │   ├── layout.tsx
│   │   ├── page.tsx            # 랜딩 페이지
│   │   ├── about/
│   │   ├── contact/
│   │   ├── blog/
│   │   │   ├── layout.tsx
│   │   │   ├── page.tsx
│   │   │   ├── loading.tsx
│   │   │   ├── [slug]/
│   │   │   ├── category/
│   │   │   └── @sidebar/       # Parallel Route
│   │   ├── docs/
│   │   │   └── [...slug]/
│   │   └── pricing/
│   ├── (auth)/                # 인증 관련
│   │   ├── layout.tsx
│   │   ├── login/
│   │   ├── register/
│   │   ├── forgot-password/
│   │   ├── reset-password/
│   │   └── verify-email/
│   ├── (dashboard)/           # 일반 사용자 대시보드
│   │   ├── layout.tsx
│   │   ├── @sidebar/          # Parallel Route - 사이드바
│   │   ├── @breadcrumb/       # Parallel Route - 브레드크럼
│   │   ├── overview/
│   │   ├── projects/
│   │   │   ├── layout.tsx
│   │   │   ├── page.tsx
│   │   │   ├── loading.tsx
│   │   │   ├── error.tsx
│   │   │   ├── [id]/
│   │   │   │   ├── page.tsx
│   │   │   │   ├── edit/
│   │   │   │   ├── members/
│   │   │   │   ├── settings/
│   │   │   │   └── @modal/     # Parallel Route - 모달
│   │   │   │       └── (.)edit/
│   │   │   └── new/
│   │   ├── teams/
│   │   │   ├── layout.tsx
│   │   │   ├── [teamId]/
│   │   │   │   ├── layout.tsx
│   │   │   │   ├── members/
│   │   │   │   ├── projects/
│   │   │   │   └── settings/
│   │   │   └── create/
│   │   ├── analytics/
│   │   │   ├── layout.tsx
│   │   │   ├── overview/
│   │   │   ├── reports/
│   │   │   └── insights/
│   │   ├── settings/
│   │   │   ├── layout.tsx
│   │   │   ├── profile/
│   │   │   ├── security/
│   │   │   ├── billing/
│   │   │   ├── notifications/
│   │   │   └── integrations/
│   │   └── help/
│   ├── (admin)/               # 관리자 영역
│   │   ├── layout.tsx
│   │   ├── loading.tsx
│   │   ├── error.tsx
│   │   ├── dashboard/
│   │   ├── users/
│   │   │   ├── layout.tsx
│   │   │   ├── page.tsx
│   │   │   ├── [id]/
│   │   │   └── bulk-actions/
│   │   ├── organizations/
│   │   ├── billing/
│   │   ├── analytics/
│   │   ├── system/
│   │   │   ├── logs/
│   │   │   ├── monitoring/
│   │   │   └── maintenance/
│   │   └── settings/
│   └── api/                   # API Routes
│       ├── auth/
│       │   ├── login/
│       │   ├── register/
│       │   ├── refresh/
│       │   └── logout/
│       ├── v1/                # API 버전 관리
│       │   ├── users/
│       │   │   ├── route.ts
│       │   │   └── [id]/
│       │   │       ├── route.ts
│       │   │       ├── avatar/
│       │   │       └── preferences/
│       │   ├── projects/
│       │   │   ├── route.ts
│       │   │   ├── [id]/
│       │   │   │   ├── route.ts
│       │   │   │   ├── members/
│       │   │   │   ├── tasks/
│       │   │   │   └── comments/
│       │   │   └── search/
│       │   ├── teams/
│       │   ├── analytics/
│       │   └── admin/
│       ├── webhooks/
│       │   ├── stripe/
│       │   ├── github/
│       │   └── slack/
│       ├── upload/
│       │   ├── avatar/
│       │   ├── documents/
│       │   └── images/
│       └── cron/              # 크론 작업
│           ├── daily-reports/
│           ├── cleanup/
│           └── backup/
├── src/                       # 소스 코드 (app 외부)
│   ├── components/
│   │   ├── ui/                # 기본 UI 컴포넌트
│   │   │   ├── forms/
│   │   │   │   ├── Input/
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   ├── Input.test.tsx
│   │   │   │   │   ├── Input.stories.tsx
│   │   │   │   │   └── Input.module.css
│   │   │   │   ├── Select/
│   │   │   │   ├── TextArea/
│   │   │   │   └── Checkbox/
│   │   │   ├── feedback/
│   │   │   │   ├── Alert/
│   │   │   │   ├── Toast/
│   │   │   │   ├── Modal/
│   │   │   │   └── Loading/
│   │   │   ├── navigation/
│   │   │   │   ├── Breadcrumb/
│   │   │   │   ├── Pagination/
│   │   │   │   ├── Tabs/
│   │   │   │   └── Menu/
│   │   │   └── data/
│   │   │       ├── Table/
│   │   │       ├── Chart/
│   │   │       ├── List/
│   │   │       └── Card/
│   │   ├── layout/            # 레이아웃 컴포넌트
│   │   │   ├── Header/
│   │   │   ├── Footer/
│   │   │   ├── Sidebar/
│   │   │   ├── Navigation/
│   │   │   └── Container/
│   │   ├── business/          # 비즈니스 로직 컴포넌트
│   │   │   ├── project/
│   │   │   │   ├── ProjectCard/
│   │   │   │   ├── ProjectForm/
│   │   │   │   ├── ProjectList/
│   │   │   │   └── ProjectDetails/
│   │   │   ├── user/
│   │   │   │   ├── UserProfile/
│   │   │   │   ├── UserCard/
│   │   │   │   ├── UserList/
│   │   │   │   └── UserForm/
│   │   │   ├── team/
│   │   │   ├── analytics/
│   │   │   └── billing/
│   │   └── providers/         # Context 프로바이더
│   │       ├── AuthProvider/
│   │       ├── ThemeProvider/
│   │       ├── QueryProvider/
│   │       └── ToastProvider/
│   ├── lib/                   # 유틸리티 라이브러리
│   │   ├── auth/
│   │   │   ├── index.ts
│   │   │   ├── providers.ts
│   │   │   ├── middleware.ts
│   │   │   └── utils.ts
│   │   ├── database/
│   │   │   ├── index.ts
│   │   │   ├── schema.ts
│   │   │   ├── migrations/
│   │   │   └── seeds/
│   │   ├── api/
│   │   │   ├── client.ts
│   │   │   ├── endpoints.ts
│   │   │   ├── middleware.ts
│   │   │   └── types.ts
│   │   ├── payments/
│   │   │   ├── stripe.ts
│   │   │   └── utils.ts
│   │   ├── email/
│   │   │   ├── templates/
│   │   │   ├── sender.ts
│   │   │   └── types.ts
│   │   ├── storage/
│   │   │   ├── s3.ts
│   │   │   ├── local.ts
│   │   │   └── types.ts
│   │   ├── analytics/
│   │   │   ├── google.ts
│   │   │   ├── mixpanel.ts
│   │   │   └── utils.ts
│   │   ├── monitoring/
│   │   │   ├── sentry.ts
│   │   │   ├── logger.ts
│   │   │   └── metrics.ts
│   │   └── utils/
│   │       ├── format.ts
│   │       ├── validation.ts
│   │       ├── date.ts
│   │       ├── string.ts
│   │       └── constants.ts
│   ├── hooks/                 # 커스텀 훅
│   │   ├── auth/
│   │   │   ├── useAuth.ts
│   │   │   ├── usePermissions.ts
│   │   │   └── useSession.ts
│   │   ├── api/
│   │   │   ├── useQuery.ts
│   │   │   ├── useMutation.ts
│   │   │   └── useInfiniteQuery.ts
│   │   ├── ui/
│   │   │   ├── useModal.ts
│   │   │   ├── useToast.ts
│   │   │   ├── useTheme.ts
│   │   │   └── useLocalStorage.ts
│   │   └── business/
│   │       ├── useProjects.ts
│   │       ├── useUsers.ts
│   │       ├── useTeams.ts
│   │       └── useAnalytics.ts
│   ├── store/                 # 상태 관리
│   │   ├── index.ts
│   │   ├── auth/
│   │   │   ├── authSlice.ts
│   │   │   └── authThunks.ts
│   │   ├── projects/
│   │   ├── users/
│   │   └── ui/
│   │       ├── modalSlice.ts
│   │       ├── themeSlice.ts
│   │       └── toastSlice.ts
│   ├── types/                 # TypeScript 타입
│   │   ├── api/
│   │   │   ├── auth.ts
│   │   │   ├── users.ts
│   │   │   ├── projects.ts
│   │   │   └── common.ts
│   │   ├── database/
│   │   │   ├── models.ts
│   │   │   └── schema.ts
│   │   ├── ui/
│   │   │   └── components.ts
│   │   └── global.ts
│   ├── styles/                # 스타일 파일
│   │   ├── components/
│   │   │   ├── forms.css
│   │   │   ├── navigation.css
│   │   │   └── feedback.css
│   │   ├── layouts/
│   │   │   ├── dashboard.css
│   │   │   └── marketing.css
│   │   ├── utilities.css
│   │   └── themes/
│   │       ├── light.css
│   │       └── dark.css
│   ├── config/                # 설정 파일
│   │   ├── app.ts
│   │   ├── database.ts
│   │   ├── auth.ts
│   │   ├── email.ts
│   │   ├── storage.ts
│   │   └── monitoring.ts
│   ├── constants/             # 상수 정의
│   │   ├── api.ts
│   │   ├── routes.ts
│   │   ├── permissions.ts
│   │   └── ui.ts
│   ├── utils/                 # 유틸리티 함수
│   │   ├── api/
│   │   │   ├── request.ts
│   │   │   ├── response.ts
│   │   │   └── validation.ts
│   │   ├── auth/
│   │   │   ├── permissions.ts
│   │   │   └── tokens.ts
│   │   ├── format/
│   │   │   ├── date.ts
│   │   │   ├── currency.ts
│   │   │   └── text.ts
│   │   └── validation/
│   │       ├── schemas.ts
│   │       └── rules.ts
│   └── data/                  # 정적 데이터 및 목업
│       ├── mock/
│       │   ├── users.ts
│       │   ├── projects.ts
│       │   └── analytics.ts
│       ├── static/
│       │   ├── plans.ts
│       │   ├── features.ts
│       │   └── countries.ts
│       └── fixtures/
│           └── test-data.ts
├── docs/                      # 프로젝트 문서
│   ├── README.md
│   ├── CONTRIBUTING.md
│   ├── DEPLOYMENT.md
│   ├── api/
│   │   └── README.md
│   ├── components/
│   │   └── README.md
│   └── architecture/
│       └── FOLDER_STRUCTURE.md
├── tests/                     # 테스트 파일
│   ├── __mocks__/
│   ├── setup.ts
│   ├── utils/
│   │   └── test-utils.tsx
│   ├── components/
│   │   └── ui/
│   ├── pages/
│   │   └── api/
│   ├── e2e/
│   │   ├── auth.spec.ts
│   │   └── projects.spec.ts
│   └── integration/
├── scripts/                   # 빌드/배포 스크립트
│   ├── build.sh
│   ├── deploy.sh
│   ├── db-migrate.js
│   └── seed-data.js
├── .github/                   # GitHub Actions
│   └── workflows/
│       ├── ci.yml
│       ├── deploy.yml
│       └── test.yml
└── public/
    ├── images/
    │   ├── avatars/
    │   ├── logos/
    │   ├── illustrations/
    │   └── backgrounds/
    ├── icons/
    │   ├── favicons/
    │   └── ui/
    ├── docs/
    └── locales/
        ├── ko.json
        ├── en.json
        └── ja.json

특수한 폴더 구조 패턴

Route Groups 활용

관련 라우트들을 논리적으로 그룹화하는 패턴입니다:

app/
├── (marketing)/               # 마케팅 페이지들
│   ├── layout.tsx            # 마케팅 레이아웃
│   ├── page.tsx              # 홈페이지
│   ├── about/
│   ├── pricing/
│   └── contact/
├── (app)/                    # 애플리케이션 페이지들
│   ├── layout.tsx            # 앱 레이아웃
│   ├── dashboard/
│   ├── projects/
│   └── settings/
├── (admin)/                  # 관리자 페이지들
│   ├── layout.tsx            # 관리자 레이아웃
│   ├── users/
│   ├── analytics/
│   └── system/
└── (auth)/                   # 인증 페이지들
    ├── login/
    ├── register/
    └── forgot-password/

Parallel Routes 구조

동시에 여러 컴포넌트를 렌더링하는 구조입니다:

app/
├── layout.tsx
├── page.tsx
├── @modal/                   # 모달 슬롯
│   ├── default.tsx
│   └── login/
│       └── page.tsx
├── @sidebar/                 # 사이드바 슬롯
│   ├── default.tsx
│   ├── loading.tsx
│   └── page.tsx
├── dashboard/
│   ├── layout.tsx           # 슬롯들을 사용하는 레이아웃
│   ├── @charts/             # 차트 슬롯
│   │   ├── default.tsx
│   │   ├── loading.tsx
│   │   └── page.tsx
│   └── @metrics/            # 메트릭 슬롯
│       ├── default.tsx
│       ├── loading.tsx
│       └── page.tsx

Intercepting Routes 패턴

모달이나 오버레이를 위한 라우트 인터셉션입니다:

app/
├── photos/
│   ├── page.tsx              # 사진 갤러리
│   └── [id]/
│       └── page.tsx          # 사진 상세 페이지
├── @modal/                   # 모달 슬롯
│   ├── default.tsx
│   └── (.)photos/            # photos 라우트 인터셉트
│       └── [id]/
│           └── page.tsx      # 모달로 표시될 사진 상세
└── layout.tsx                # 모달 슬롯을 포함한 레이아웃

베스트 프랙티스

네이밍 컨벤션

// 파일 네이밍
// ✅ 좋은 예
components/
├── UserProfile.tsx           # PascalCase for components
├── useUserData.ts           # camelCase for hooks
├── api-client.ts            # kebab-case for utilities
└── user-types.ts            # kebab-case for types

// ✅ 폴더 네이밍
app/
├── user-management/          # kebab-case for routes
├── api/
│   └── user-profile/        # kebab-case for API routes
└── (auth)/                  # parentheses for route groups

절대 경로 설정

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@/components/*": ["./src/components/*"],
      "@/lib/*": ["./src/lib/*"],
      "@/types/*": ["./src/types/*"],
      "@/hooks/*": ["./src/hooks/*"],
      "@/styles/*": ["./src/styles/*"],
      "@/config/*": ["./src/config/*"],
      "@/utils/*": ["./src/utils/*"]
    }
  }
}

컴포넌트 구조 패턴

components/
├── ui/
│   └── Button/
│       ├── index.tsx         # 메인 컴포넌트
│       ├── Button.test.tsx   # 테스트
│       ├── Button.stories.tsx # Storybook
│       ├── Button.module.css # 스타일
│       └── types.ts          # 타입 정의
// components/ui/Button/index.tsx
import React from 'react'
import styles from './Button.module.css'
import { ButtonProps } from './types'

export const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  size = 'medium',
  children,
  ...props
}) => {
  return (
    <button
      className={`${styles.button} ${styles[variant]} ${styles[size]}`}
      {...props}
    >
      {children}
    </button>
  )
}

export default Button
export type { ButtonProps } from './types'

API 조직화

app/api/
├── v1/                       # 버전 관리
│   ├── users/
│   │   ├── route.ts         # GET, POST /api/v1/users
│   │   └── [id]/
│   │       ├── route.ts     # GET, PUT, DELETE /api/v1/users/[id]
│   │       ├── avatar/
│   │       │   └── route.ts # POST /api/v1/users/[id]/avatar
│   │       └── preferences/
│   │           └── route.ts # GET, PUT /api/v1/users/[id]/preferences
│   └── projects/
├── auth/                    # 인증 관련 API
│   ├── login/
│   ├── logout/
│   └── refresh/
└── webhooks/                # 웹훅 처리
    ├── stripe/
    └── github/

팀별 폴더 구조 전략

기능별 조직화 (Feature-based)

src/
├── features/
│   ├── authentication/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── services/
│   │   ├── types/
│   │   └── index.ts
│   ├── projects/
│   │   ├── components/
│   │   │   ├── ProjectCard/
│   │   │   ├── ProjectForm/
│   │   │   └── ProjectList/
│   │   ├── hooks/
│   │   │   ├── useProjects.ts
│   │   │   └── useProjectMutations.ts
│   │   ├── services/
│   │   │   ├── api.ts
│   │   │   └── validation.ts
│   │   ├── types/
│   │   │   └── index.ts
│   │   └── index.ts
│   ├── users/
│   └── billing/
└── shared/                  # 공유 리소스
    ├── components/
    ├── hooks/
    ├── utils/
    └── types/

레이어별 조직화 (Layer-based)

src/
├── presentation/            # 프레젠테이션 레이어
│   ├── components/
│   ├── pages/
│   └── hooks/
├── business/                # 비즈니스 로직 레이어
│   ├── services/
│   ├── models/
│   └── rules/
├── data/                    # 데이터 레이어
│   ├── repositories/
│   ├── api/
│   └── cache/
└── infrastructure/          # 인프라스트럭처 레이어
    ├── database/
    ├── external-services/
    └── monitoring/

도메인별 조직화 (Domain-based)

src/
├── domains/
│   ├── user-management/
│   │   ├── entities/
│   │   ├── repositories/
│   │   ├── services/
│   │   ├── components/
│   │   └── api/
│   ├── project-management/
│   │   ├── entities/
│   │   ├── repositories/
│   │   ├── services/
│   │   ├── components/
│   │   └── api/
│   └── billing/
└── shared-kernel/           # 공유 도메인 로직
    ├── entities/
    ├── value-objects/
    └── services/

성능 최적화를 위한 구조

코드 스플리팅 전략

// 동적 임포트를 위한 구조
app/
├── dashboard/
│   ├── page.tsx             # 기본 대시보드
│   ├── analytics/
│   │   └── page.tsx         # 분석 페이지 (지연 로딩)
│   └── reports/
│       └── page.tsx         # 리포트 페이지 (지연 로딩)

// components/LazyComponents.tsx
import dynamic from 'next/dynamic'
import LoadingSpinner from './LoadingSpinner'

export const AnalyticsChart = dynamic(
  () => import('./AnalyticsChart'),
  {
    loading: () => <LoadingSpinner />,
    ssr: false // 클라이언트에서만 로드
  }
)

export const HeavyDataTable = dynamic(
  () => import('./HeavyDataTable'),
  {
    loading: () => <div>테이블을 로드하는 중...</div>
  }
)

메타데이터 관리

app/
├── sitemap.ts               # 사이트맵 생성
├── robots.ts                # 로봇 규칙
├── opengraph-image.tsx      # OpenGraph 이미지
├── favicon.ico              # 파비콘
└── manifest.ts              # 웹 앱 매니페스트

국제화 (i18n) 구조

locales/
├── ko/
│   ├── common.json
│   ├── auth.json
│   ├── dashboard.json
│   └── errors.json
├── en/
│   ├── common.json
│   ├── auth.json
│   ├── dashboard.json
│   └── errors.json
└── ja/

// i18n 설정
lib/
├── i18n/
│   ├── config.ts
│   ├── server.ts
│   ├── client.ts
│   └── middleware.ts

마무리

Next.js 15 App Router의 폴더 구조는 프로젝트 규모와 팀 구성, 비즈니스 요구사항에 따라 유연하게 설계되어야 합니다.

핵심 원칙:

  1. 일관성: 팀 전체가 따를 수 있는 명확한 규칙
  2. 확장성: 프로젝트 성장에 따른 확장 가능성
  3. 가독성: 새로운 팀원이 쉽게 이해할 수 있는 구조
  4. 유지보수성: 코드 변경과 리팩토링이 용이한 구조
  5. 성능: 번들 크기와 로딩 성능을 고려한 구조

선택 기준:

  • 소규모 프로젝트: 단순하고 직관적인 구조
  • 중규모 프로젝트: 기능별 분리와 재사용성 고려
  • 대규모 프로젝트: 도메인 중심 설계와 레이어 분리

적절한 폴더 구조는 개발 생산성을 높이고 코드 품질을 향상시키는 핵심 요소입니다. 프로젝트의 특성에 맞는 구조를 선택하고 지속적으로 개선해나가는 것이 중요합니다.




댓글 남기기