# 에러 및 특수 페이지 구성 가이드

## 📋 개요

Next.js 15 App Router에서 404, 에러, 로딩 페이지 등 특수 페이지 구성 방법 및 우선순위 규칙

---

## 🎯 생성된 페이지 목록

### 1. 404 Not Found 페이지

| 파일 경로 | 적용 범위 | 레이아웃 포함 |
|-----------|----------|-------------|
| `app/[locale]/not-found.tsx` | 전역 (모든 경로) | ❌ 없음 |
| `app/[locale]/(protected)/not-found.tsx` | 보호된 경로만 | ✅ DashboardLayout |

### 2. Error Boundary 페이지

| 파일 경로 | 적용 범위 | 레이아웃 포함 |
|-----------|----------|-------------|
| `app/[locale]/error.tsx` | 전역 에러 | ❌ 없음 |
| `app/[locale]/(protected)/error.tsx` | 보호된 경로 에러 | ✅ DashboardLayout |

### 3. Loading 페이지

| 파일 경로 | 적용 범위 | 레이아웃 포함 |
|-----------|----------|-------------|
| `app/[locale]/(protected)/loading.tsx` | 보호된 경로 로딩 | ✅ DashboardLayout |

---

## 📁 파일 구조

```
src/app/
├── [locale]/
│   ├── not-found.tsx                    # ✅ 전역 404 (레이아웃 없음)
│   ├── error.tsx                        # ✅ 전역 에러 (레이아웃 없음)
│   │
│   └── (protected)/
│       ├── layout.tsx                   # 🎨 공통 레이아웃 (인증 + DashboardLayout)
│       ├── not-found.tsx                # ✅ Protected 404 (레이아웃 포함)
│       ├── error.tsx                    # ✅ Protected 에러 (레이아웃 포함)
│       ├── loading.tsx                  # ✅ Protected 로딩 (레이아웃 포함)
│       │
│       ├── dashboard/
│       │   └── page.tsx                 # 실제 대시보드 페이지
│       │
│       └── [...slug]/
│           └── page.tsx                 # 🔄 Catch-all (메뉴 기반 라우팅)
│                                        #    - 메뉴에 있는 경로 → EmptyPage
│                                        #    - 메뉴에 없는 경로 → not-found.tsx
```

---

## 🔍 페이지별 상세 설명

### 1. not-found.tsx (404 페이지)

#### 전역 404 (`app/[locale]/not-found.tsx`)

```typescript
// ✅ 특징:
// - 서버 컴포넌트 (async/await 가능)
// - 'use client' 불필요
// - 레이아웃 없음 (전체 화면)
// - metadata 지원 가능

export default function NotFoundPage() {
  return (
    <div>404 - 페이지를 찾을 수 없습니다</div>
  );
}
```

**트리거:**
- 존재하지 않는 URL 접근
- `notFound()` 함수 호출

#### Protected 404 (`app/[locale]/(protected)/not-found.tsx`)

```typescript
// ✅ 특징:
// - DashboardLayout 자동 적용 (사이드바, 헤더)
// - 인증된 사용자만 볼 수 있음
// - 보호된 경로 내 404만 처리

export default function ProtectedNotFoundPage() {
  return (
    <div>보호된 경로에서 페이지를 찾을 수 없습니다</div>
  );
}
```

---

### 2. error.tsx (에러 바운더리)

#### 전역 에러 (`app/[locale]/error.tsx`)

```typescript
'use client'; // ✅ 필수!

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>오류 발생: {error.message}</h2>
      <button onClick={reset}>다시 시도</button>
    </div>
  );
}
```

**Props:**
- `error`: 발생한 에러 객체
  - `message`: 에러 메시지
  - `digest`: 에러 고유 ID (서버 로깅용)
- `reset`: 에러 복구 함수 (컴포넌트 재렌더링)

**특징:**
- **'use client' 필수** - React Error Boundary는 클라이언트 전용
- 하위 경로의 모든 에러 포착
- 이벤트 핸들러 에러는 포착 불가
- 루트 layout 에러는 포착 불가 (global-error.tsx 필요)

#### Protected 에러 (`app/[locale]/(protected)/error.tsx`)

```typescript
'use client'; // ✅ 필수!

export default function ProtectedError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    // DashboardLayout 자동 적용됨
    <div>보호된 경로에서 오류 발생</div>
  );
}
```

---

### 3. loading.tsx (로딩 상태)

#### Protected 로딩 (`app/[locale]/(protected)/loading.tsx`)

```typescript
// ✅ 특징:
// - 서버/클라이언트 모두 가능
// - React Suspense 자동 적용
// - DashboardLayout 유지

export default function ProtectedLoading() {
  return (
    <div>페이지를 불러오는 중...</div>
  );
}
```

**동작 방식:**
- `page.js`와 하위 요소를 자동으로 `<Suspense>` 경계로 감쌈
- 페이지 전환 시 즉각적인 로딩 UI 표시
- 네비게이션 중단 가능

---

## 🔄 우선순위 규칙

Next.js는 **가장 가까운 부모 세그먼트**의 파일을 사용합니다.

### 404 우선순위

```
/dashboard/settings 접근 시:

1. dashboard/settings/not-found.tsx      (가장 높음)
2. dashboard/not-found.tsx
3. (protected)/not-found.tsx             ✅ 현재 사용됨
4. [locale]/not-found.tsx                (폴백)
5. app/not-found.tsx                     (최종 폴백)
```

### 에러 우선순위

```
/dashboard 에서 에러 발생 시:

1. dashboard/error.tsx
2. (protected)/error.tsx                 ✅ 현재 사용됨
3. [locale]/error.tsx                    (폴백)
4. app/error.tsx                         (최종 폴백)
5. global-error.tsx                      (루트 layout 에러만)
```

---

## 🎨 레이아웃 적용 규칙

### 레이아웃 없는 페이지 (전역)

```
app/[locale]/not-found.tsx
app/[locale]/error.tsx
```

**특징:**
- 전체 화면 사용
- 사이드바, 헤더 없음
- 로그인 전/후 모두 접근 가능

**용도:**
- 로그인 페이지에서 404
- 전역 에러 (로그인 실패 등)

### 레이아웃 포함 페이지 (Protected)

```
app/[locale]/(protected)/not-found.tsx
app/[locale]/(protected)/error.tsx
app/[locale]/(protected)/loading.tsx
```

**특징:**
- DashboardLayout 자동 적용
- 사이드바, 헤더 유지
- 인증된 사용자만 접근

**용도:**
- 대시보드 내 404
- 보호된 페이지 에러
- 페이지 로딩 상태

---

## 🚨 'use client' 규칙

| 파일 | 필수 여부 | 이유 |
|------|-----------|------|
| `error.tsx` | ✅ **필수** | React Error Boundary는 클라이언트 전용 |
| `global-error.tsx` | ✅ **필수** | Error Boundary + 상태 관리 |
| `not-found.tsx` | ❌ 선택 | 서버 컴포넌트 가능 (metadata 지원) |
| `loading.tsx` | ❌ 선택 | 서버 컴포넌트 가능 (정적 UI 권장) |

**에러 예시:**

```typescript
// ❌ 잘못된 코드 - error.tsx에 'use client' 없음
export default function Error({ error, reset }) {
  // Error: Error boundaries must be Client Components
}

// ✅ 올바른 코드
'use client';

export default function Error({ error, reset }) {
  // 정상 작동
}
```

---

## 🔄 Catch-all 라우트와 메뉴 기반 라우팅

### 개요

`app/[locale]/(protected)/[...slug]/page.tsx` 파일은 **메뉴 기반 동적 라우팅**을 구현합니다.

### 동작 로직

```typescript
'use client';

import { notFound } from 'next/navigation';
import { EmptyPage } from '@/components/common/EmptyPage';

export default function CatchAllPage({ params }: PageProps) {
  const [isValidPath, setIsValidPath] = useState<boolean | null>(null);

  useEffect(() => {
    // 1. localStorage에서 사용자 메뉴 데이터 가져오기
    const userData = JSON.parse(localStorage.getItem('user'));
    const menus = userData.menu || [];

    // 2. 요청된 경로가 메뉴에 있는지 확인
    const requestedPath = `/${slug.join('/')}`;
    const isPathInMenu = checkMenuRecursively(menus, requestedPath);

    // 3. 메뉴 존재 여부에 따라 분기
    setIsValidPath(isPathInMenu);
  }, [params]);

  // 메뉴에 없는 경로 → 404
  if (!isValidPath) {
    notFound();
  }

  // 메뉴에 있지만 구현되지 않은 페이지 → EmptyPage
  return <EmptyPage />;
}
```

### 라우팅 결정 트리

```
사용자가 /base/product/lists 접근
│
├─ 1️⃣ localStorage에서 user.menu 읽기
│   └─ 메뉴 데이터: [{path: '/base/product/lists', ...}, ...]
│
├─ 2️⃣ 경로 검증
│   ├─ ✅ 메뉴에 경로 존재
│   │   └─ EmptyPage 표시 (구현 예정 페이지)
│   │
│   └─ ❌ 메뉴에 경로 없음
│       └─ notFound() 호출 → not-found.tsx
│
└─ 3️⃣ 최종 결과
    ├─ 메뉴에 있음: EmptyPage (DashboardLayout 포함)
    └─ 메뉴에 없음: not-found.tsx (DashboardLayout 포함)
```

### 사용 예시

#### 케이스 1: 메뉴에 있는 경로 (구현 안됨)

```bash
# 사용자 메뉴에 /base/product/lists가 있는 경우
http://localhost:3000/ko/base/product/lists
→ ✅ EmptyPage 표시 (사이드바, 헤더 유지)
```

#### 케이스 2: 메뉴에 없는 엉뚱한 경로

```bash
# 사용자 메뉴에 /fake-page가 없는 경우
http://localhost:3000/ko/fake-page
→ ❌ not-found.tsx 표시 (사이드바, 헤더 유지)
```

#### 케이스 3: 실제 구현된 페이지

```bash
# dashboard/page.tsx가 실제로 존재
http://localhost:3000/ko/dashboard
→ ✅ Dashboard 컴포넌트 표시
```

### 메뉴 데이터 구조

```typescript
// localStorage에 저장되는 메뉴 구조 (로그인 시 받아옴)
{
  menu: [
    {
      id: "1",
      label: "기초정보관리",
      path: "/base",
      children: [
        {
          id: "1-1",
          label: "제품관리",
          path: "/base/product/lists"
        },
        {
          id: "1-2",
          label: "거래처관리",
          path: "/base/company/lists"
        }
      ]
    },
    {
      id: "2",
      label: "시스템관리",
      path: "/system",
      children: [
        {
          id: "2-1",
          label: "사용자관리",
          path: "/system/user/lists"
        }
      ]
    }
  ]
}
```

### 장점

1. **동적 메뉴 관리**: 백엔드에서 메뉴 구조 변경 시 프론트엔드 코드 수정 불필요
2. **권한 기반 라우팅**: 사용자별 메뉴가 다르면 접근 가능한 경로도 다름
3. **명확한 UX**:
   - 메뉴에 있는 페이지 (미구현) → "준비 중" 메시지
   - 메뉴에 없는 페이지 → "404 Not Found"

### 디버깅

개발 모드에서는 콘솔에 디버그 로그가 출력됩니다:

```typescript
console.log('🔍 요청된 경로:', requestedPath);
console.log('📋 메뉴 데이터:', menus);
console.log('  - 비교 중:', item.path, 'vs', path);
console.log('📌 경로 존재 여부:', pathExists);
```

---

## 💡 실전 사용 예시

### 1. 404 테스트

```typescript
// 존재하지 않는 경로 접근
/non-existent-page
→ app/[locale]/not-found.tsx 표시

// 보호된 경로에서 404
/dashboard/unknown-page
→ app/[locale]/(protected)/not-found.tsx 표시 (레이아웃 포함)
```

### 2. 에러 발생 시뮬레이션

```typescript
// page.tsx
export default function TestPage() {
  // 의도적으로 에러 발생
  throw new Error('테스트 에러');

  return <div>페이지</div>;
}

// → error.tsx가 에러 포착
```

### 3. 프로그래매틱 404

```typescript
import { notFound } from 'next/navigation';

export default function ProductPage({ params }: { params: { id: string } }) {
  const product = getProduct(params.id);

  if (!product) {
    notFound(); // ← not-found.tsx 표시
  }

  return <div>{product.name}</div>;
}
```

### 4. 에러 복구

```typescript
'use client';

export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div>
      <h2>오류 발생: {error.message}</h2>
      <button onClick={() => reset()}>
        다시 시도 {/* ← 컴포넌트 재렌더링 */}
      </button>
    </div>
  );
}
```

---

## 🐛 개발 환경 vs 프로덕션

### 개발 환경 (development)

```typescript
// 에러 상세 정보 표시
{process.env.NODE_ENV === 'development' && (
  <div>
    <p>에러 메시지: {error.message}</p>
    <p>스택 트레이스: {error.stack}</p>
  </div>
)}
```

**특징:**
- 에러 오버레이 표시
- 상세한 에러 정보
- Hot Reload 지원

### 프로덕션 (production)

```typescript
// 사용자 친화적 메시지만 표시
<div>
  <p>일시적인 오류가 발생했습니다.</p>
  <button onClick={reset}>다시 시도</button>
</div>
```

**특징:**
- 간결한 에러 메시지
- 보안 정보 숨김
- 에러 로깅 (Sentry 등)

---

## 📌 체크리스트

### 404 페이지

- [ ] 전역 404 페이지 생성 (`app/[locale]/not-found.tsx`)
- [ ] Protected 404 페이지 생성 (`app/[locale]/(protected)/not-found.tsx`)
- [ ] 레이아웃 적용 확인
- [ ] 다국어 지원 (선택사항)
- [ ] 버튼 링크 동작 테스트

### 에러 페이지

- [ ] 'use client' 지시어 추가 확인
- [ ] Props 타입 정의 (`error`, `reset`)
- [ ] 개발/프로덕션 환경 분기
- [ ] 에러 로깅 추가 (선택사항)
- [ ] 복구 버튼 동작 테스트

### 로딩 페이지

- [ ] 로딩 UI 디자인 일관성
- [ ] 레이아웃 내 표시 확인
- [ ] Suspense 경계 테스트

### Catch-all 라우트 (메뉴 기반 라우팅)

- [x] localStorage 메뉴 데이터 검증 로직 구현
- [x] 메뉴에 있는 경로 → EmptyPage 분기
- [x] 메뉴에 없는 경로 → not-found.tsx 분기
- [x] 재귀적 메뉴 트리 탐색 구현
- [ ] 디버그 로그 프로덕션 제거
- [ ] 성능 최적화 (메뉴 데이터 캐싱)

---

## 🔗 관련 문서

- [Empty Page Configuration](./[IMPL-2025-11-11]%20empty-page-configuration.md)
- [Route Protection Architecture](./[IMPL-2025-11-07]%20route-protection-architecture.md)
- [Authentication Implementation Guide](./[IMPL-2025-11-07]%20authentication-implementation-guide.md)

---

## 📚 참고 자료

- [Next.js 15 Error Handling](https://nextjs.org/docs/app/building-your-application/routing/error-handling)
- [Next.js 15 Not Found](https://nextjs.org/docs/app/api-reference/file-conventions/not-found)
- [Next.js 15 Loading UI](https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming)

---

**작성일:** 2025-11-11
**작성자:** Claude Code
**마지막 수정:** 2025-11-12 (Catch-all 라우트 메뉴 기반 로직 추가)