minstudio

앱 라우터 기초: 파일 기반 라우팅, 레이아웃, 내비게이션

Next.js의 App Router는 폴더 구조가 곧 웹사이트의 주소(URL)가 되는 직관적인 파일 시스템 기반 라우팅을 제공합니다. 개발자는 복잡한 라우터 설정 코드 없이 폴더를 생성하는 것만으로 페이지를 만들 수 있습니다.

📁 폴더 구조가 URL 경로로 변환되는 과정 폴더 구조 (src/app) app/ 📄 page.tsx about/ 📄 page.tsx blog/ [slug]/ 📄 page.tsx (auth)/ login/ 📄 page.tsx 매칭되는 URL 경로 / (메인 접속) /about /blog/hello-world 💡 동적 라우팅 (slug 변수에 값 할당) /login 💡 라우트 그룹 ((auth)는 URL에서 제외됨)

1. 기본 라우팅 원칙

Next.js의 라우팅은 엄격한 규칙 하나를 따릅니다. "폴더 이름은 URL 경로가 되고, 그 경로가 화면에 보이려면 반드시 page.tsx 파일이 존재해야 한다."

  • app/page.tsx/ (메인 페이지)
  • app/dashboard/page.tsx/dashboard
  • app/dashboard/settings/page.tsx/dashboard/settings

만약 폴더를 만들었더라도 내부에 page.tsx가 없다면 해당 경로는 브라우저에서 접근할 수 없는 404가 됩니다. 이를 통해 컴포넌트, 스타일, 테스트 파일 등을 안전하게 같은 폴더 내에 배치할 수 있습니다 (Colocation 패턴).

2. 동적 라우팅 (Dynamic Routes)

블로그 포스트, 쇼핑몰 상품 상세 페이지처럼 미리 경로를 알 수 없는 경우 대괄호 [folderName]를 사용하여 동적 라우팅을 구현합니다.

  • app/blog/[slug]/page.tsx/blog/react-guide, /blog/nextjs-tips 매칭
  • 이 때 대괄호 안의 이름(slug)은 페이지 컴포넌트의 params 객체로 전달됩니다.
    const { slug } = await params;

3. 라우트 그룹 (Route Groups)

프로젝트 규모가 커지면 연관된 페이지들을 묶어서 관리하고 싶지만, URL 경로가 길어지는 것은 원치 않을 수 있습니다. 이때 소괄호 (folderName)를 사용합니다.

소괄호로 감싼 폴더는 라우팅 구조를 그룹화하는 역할만 하며, 실제 브라우저 URL 경로에는 영향을 주지 않습니다. 주로 공통 레이아웃을 그룹별로 다르게 적용할 때 매우 유용합니다.

  • app/(auth)/login/page.tsx/login
  • app/(auth)/register/page.tsx/register
  • app/(marketing)/about/page.tsx/about

4. 역할을 가지는 특수 파일들 (Special Files)

폴더 안에 배치되는 특정 이름을 가진 파일들은 각자의 고유한 역할을 가집니다.

page.tsx
고유한 경로의 메인 UI를 정의합니다. (가장 필수적인 파일)
layout.tsx
하위 폴더의 모든 page.tsx를 감싸는 공통 레이아웃(헤더, 사이드바 등)을 정의합니다. 페이지 이동 시에도 상태가 보존되며 다시 렌더링되지 않습니다.
loading.tsx
서버 컴포넌트의 데이터 페칭 중에 보여줄 로딩 UI (스피너, 스켈레톤 등)를 정의합니다. React Suspense 기반으로 동작합니다.
error.tsx
해당 라우트에서 예외가 발생했을 때 보여줄 에러 화면입니다. (반드시 'use client'여야 함)
not-found.tsx
요청한 페이지나 리소스를 찾을 수 없을 때(404) 표시되는 커스텀 UI입니다.
// ==========================================
// 📂 app/(shop)/product/[id]/page.tsx
// 동적 라우팅 페이지 예시
// ==========================================

// App Router의 페이지 컴포넌트는 기본적으로 서버 컴포넌트입니다.
export default async function ProductDetailPage({ 
  params 
}: { 
  params: Promise<{ id: string }> 
}) {
  // params 객체에서 동적 경로 값(id)을 비동기로 추출합니다. (Next.js 15+ 권장)
  const { id } = await params;
  
  // 데이터베이스나 API에서 상품 정보를 조회 (서버단에서 실행됨)
  // const product = await getProductById(id);

  return (
    <div className="p-8">
      <h1 className="text-2xl font-bold">상품 상세 페이지</h1>
      <p className="mt-4 text-gray-600">
        조회 요청된 상품 ID: <span className="text-blue-500 font-bold">{id}</span>
      </p>
    </div>
  );
}
앱 라우터 기초: 파일 기반 라우팅, 레이아웃, 내비게이션 | Minstudio