;
+}
+```
+
+### 2. 메시지 키 누락
+
+모든 언어 파일에 동일한 키가 있어야 합니다.
+
+```json
+// ❌ ko.json에는 있지만 en.json에 없는 경우
+// ko.json
+{ "newFeature": "새 기능" }
+
+// en.json
+{} // 누락!
+```
+
+**해결**: 모든 언어 파일에 키 추가
+
+### 3. 동적 라우팅
+
+```typescript
+// ❌ 로케일 없이 하드코딩
+Dashboard
+
+// ✅ 로케일 포함
+Dashboard
+```
+
+---
+
+## 🔗 참고 자료
+
+- [next-intl 공식 문서](https://next-intl-docs.vercel.app/)
+- [Next.js Internationalization](https://nextjs.org/docs/app/building-your-application/routing/internationalization)
+- [ICU Message Format](https://unicode-org.github.io/icu/userguide/format_parse/messages/)
+
+---
+
+## 📝 변경 이력
+
+| 날짜 | 버전 | 변경 내용 |
+|-----|------|---------|
+| 2025-11-06 | 1.0.0 | 초기 i18n 설정 구현 (ko, en, ja 지원) |
+
+---
+
+## 💡 팁
+
+### 번역 키 네이밍 규칙
+
+```
+패턴: {네임스페이스}.{카테고리}.{키}
+
+예시:
+- common.buttons.save
+- auth.form.emailPlaceholder
+- validation.errors.required
+- navigation.menu.dashboard
+```
+
+### 메시지 파일 관리
+
+```bash
+# 번역 누락 확인 스크립트 (package.json에 추가)
+{
+ "scripts": {
+ "i18n:check": "node scripts/check-translations.js"
+ }
+}
+```
+
+### 성능 최적화
+
+- **Code Splitting**: 네임스페이스별로 메시지 파일 분리
+- **Dynamic Import**: 필요한 언어만 로드
+- **Caching**: 번역 결과 메모이제이션
+
+---
+
+**문서 작성일**: 2025-11-06
+**작성자**: Claude Code
+**프로젝트**: Multi-tenant ERP System
\ No newline at end of file
diff --git a/docs/[IMPL-2025-11-07] api-key-management.md b/docs/[IMPL-2025-11-07] api-key-management.md
new file mode 100644
index 00000000..a8dacd81
--- /dev/null
+++ b/docs/[IMPL-2025-11-07] api-key-management.md
@@ -0,0 +1,306 @@
+# API Key 관리 가이드
+
+## 📋 개요
+
+PHP 백엔드에서 발급하는 API Key의 안전한 관리 및 주기적 갱신 대응 방법
+
+---
+
+## 🔑 현재 API Key 정보
+
+```yaml
+개발용 API Key:
+ 키 값: 42Jfwc6EaRQ04GNRmLR5kzJp5UudSOzGGqjmdk1a
+ 발급일: 2025-11-07
+ 용도: 개발 환경 고정 키
+ 갱신: 주기적으로 변동 가능
+```
+
+---
+
+## 🔐 보안 원칙
+
+### ✅ DO (반드시 해야 할 것)
+- `.env.local`에만 실제 키 저장
+- 서버 사이드 코드에서만 사용
+- Git에 절대 커밋 금지
+- 팀 공유 문서로 키 관리
+
+### ❌ DON'T (절대 하지 말 것)
+- 하드코딩 금지
+- `NEXT_PUBLIC_` 접두사 사용 금지
+- 브라우저 코드에서 사용 금지
+- 공개 저장소에 업로드 금지
+
+---
+
+## 📁 파일 구성
+
+### .env.local (실제 키 - Git 제외)
+```env
+# API Key (서버 사이드 전용 - 절대 공개 금지!)
+# 개발용 고정 키 (주기적 갱신 예정)
+# 발급일: 2025-11-07
+# 갱신 필요 시: PHP 백엔드 팀에 새 키 요청
+API_KEY=42Jfwc6EaRQ04GNRmLR5kzJp5UudSOzGGqjmdk1a
+```
+
+### .env.example (템플릿 - Git 커밋 OK)
+```env
+# API Key (⚠️ 서버 사이드 전용 - 절대 공개 금지!)
+# 개발팀 공유: 팀 내부 문서에서 키 값 확인
+# 주기적 갱신: PHP 백엔드 팀에서 새 키 발급 시 업데이트 필요
+API_KEY=your-secret-api-key-here
+```
+
+### .gitignore 확인
+```bash
+# 라인 100-101에 이미 포함됨
+.env.local
+.env*.local
+```
+
+---
+
+## 🔄 API Key 갱신 프로세스
+
+### 1️⃣ PHP 팀에서 새 키 발급
+```
+PHP 백엔드 팀 → 새 API Key 발급
+ ↓
+ 팀 공유 문서 업데이트
+```
+
+### 2️⃣ 로컬 개발 환경 업데이트
+```bash
+# .env.local 파일 열기
+vi .env.local
+
+# 또는
+code .env.local
+
+# API_KEY 값만 변경
+API_KEY=새로운키값여기에입력
+
+# 개발 서버 재시작
+npm run dev
+```
+
+### 3️⃣ 프로덕션 환경 업데이트
+
+#### Vercel 배포
+```bash
+# CLI로 업데이트
+vercel env add API_KEY production
+
+# 또는 대시보드에서
+# Settings → Environment Variables → API_KEY 편집
+```
+
+#### AWS/기타 환경
+```bash
+# 환경 변수 업데이트
+export API_KEY=새로운키값
+
+# 또는 배포 설정에서 환경 변수 수정
+```
+
+### 4️⃣ 검증
+```bash
+# 개발 서버 시작 시 자동으로 검증됨
+npm run dev
+
+# 콘솔 출력 확인:
+# 🔐 API Key Configuration:
+# ├─ Configured: ✅
+# ├─ Valid Format: ✅
+# ├─ Masked Key: 42Jf********************dk1a
+# └─ Length: 48 chars
+```
+
+---
+
+## 🛠️ API Key 검증 유틸리티
+
+### 자동 검증 기능
+```typescript
+// lib/api/auth/api-key-validator.ts
+import { apiKeyValidator } from '@/lib/api/auth/api-key-validator';
+
+// 개발 서버 시작 시 자동 실행
+console.log(apiKeyValidator.getDebugInfo());
+
+// 출력 예시:
+// API Key Status:
+// ├─ Configured: ✅
+// ├─ Valid Format: ✅
+// ├─ Masked Key: 42Jf********************dk1a
+// └─ Length: 48 chars
+```
+
+### 수동 검증
+```typescript
+import { apiKeyValidator } from '@/lib/api/auth/api-key-validator';
+
+// API Key 존재 확인
+if (!apiKeyValidator.isConfigured()) {
+ console.error('API Key not configured!');
+}
+
+// 형식 검증
+if (!apiKeyValidator.isValid()) {
+ console.error('Invalid API Key format!');
+}
+
+// 디버그 정보 출력
+console.log(apiKeyValidator.getDebugInfo());
+```
+
+---
+
+## 📊 사용 예시
+
+### 서버 사이드 (Next.js API Route)
+```typescript
+// app/api/sync/route.ts
+import { createApiKeyClient } from '@/lib/api/auth/api-key-client';
+
+export async function GET() {
+ try {
+ // 환경 변수에서 자동으로 키를 가져옴
+ const client = createApiKeyClient();
+
+ const data = await client.fetchData('/api/external-data');
+
+ return Response.json({ success: true, data });
+ } catch (error) {
+ console.error('API request failed:', error);
+ return Response.json(
+ { error: 'Failed to fetch data' },
+ { status: 500 }
+ );
+ }
+}
+```
+
+### 백그라운드 스크립트
+```typescript
+// scripts/sync-data.ts
+import { createApiKeyClient } from '@/lib/api/auth/api-key-client';
+import { apiKeyValidator } from '@/lib/api/auth/api-key-validator';
+
+async function syncData() {
+ // 1. 환경 변수 확인
+ console.log(apiKeyValidator.getDebugInfo());
+
+ if (!apiKeyValidator.isValid()) {
+ throw new Error('Invalid API Key configuration');
+ }
+
+ // 2. API 요청
+ const client = createApiKeyClient();
+ const data = await client.fetchData('/api/sync-endpoint');
+
+ console.log('Sync completed:', data);
+}
+
+syncData().catch(console.error);
+```
+
+---
+
+## ⚠️ 에러 처리
+
+### API Key 미설정
+```
+❌ API_KEY is not configured!
+📝 Please check:
+ 1. .env.local file exists
+ 2. API_KEY is set correctly
+ 3. Restart development server (npm run dev)
+
+💡 Contact backend team if you need a new API key.
+```
+
+**해결 방법:**
+1. `.env.local` 파일 생성 확인
+2. `API_KEY=실제키값` 입력
+3. `npm run dev` 재시작
+
+### API Key 형식 오류
+```
+❌ Invalid API Key format!
+ - Minimum 32 characters required
+ - Only alphanumeric characters allowed
+```
+
+**해결 방법:**
+1. PHP 팀에서 발급받은 키 확인
+2. 복사 시 공백/줄바꿈 없는지 확인
+3. 정확한 키 값 재입력
+
+---
+
+## 🔍 만료 경고 (선택사항)
+
+### 만료 체크 기능
+```typescript
+// lib/api/auth/key-expiry-check.ts
+import { apiKeyValidator } from './api-key-validator';
+
+// API Key 발급일
+const issuedDate = new Date('2025-11-07');
+
+// 90일 유효기간으로 체크
+const status = apiKeyValidator.checkExpiry(issuedDate, 90);
+
+console.log(status.message);
+// ✅ API Key valid (75 days left)
+// ⚠️ API Key expiring in 10 days
+// 🔴 API Key expired! Contact backend team.
+
+if (status.isExpiring) {
+ console.warn('⚠️ Please contact backend team for new API key!');
+}
+```
+
+---
+
+## 📚 체크리스트
+
+### 초기 설정
+- [ ] `.env.local` 파일 생성
+- [ ] `API_KEY` 값 입력
+- [ ] `.gitignore`에 `.env.local` 포함 확인
+- [ ] 개발 서버 시작 후 검증 확인
+
+### 키 갱신 시
+- [ ] PHP 팀에서 새 키 수령
+- [ ] `.env.local` 업데이트
+- [ ] 로컬 개발 서버 재시작
+- [ ] 검증 로그 확인
+- [ ] 프로덕션 환경 변수 업데이트
+
+### 보안 점검
+- [ ] Git에 `.env.local` 커밋 안됨
+- [ ] 브라우저 코드에서 사용 안함
+- [ ] `NEXT_PUBLIC_` 접두사 없음
+- [ ] 팀 공유 문서에 키 기록
+
+---
+
+## 🚀 다음 단계
+
+API Key 설정 완료 후:
+1. `createApiKeyClient()` 사용하여 API 요청
+2. 서버 사이드 코드에서만 호출
+3. 에러 발생 시 검증 로그 확인
+4. 주기적으로 만료 시간 체크 (선택)
+
+---
+
+## 📞 문의
+
+- **API Key 발급**: PHP 백엔드 팀
+- **기술 지원**: 프론트엔드 팀
+- **보안 문제**: DevOps/보안 팀
\ No newline at end of file
diff --git a/docs/[IMPL-2025-11-07] auth-guard-usage.md b/docs/[IMPL-2025-11-07] auth-guard-usage.md
new file mode 100644
index 00000000..b0c44ef1
--- /dev/null
+++ b/docs/[IMPL-2025-11-07] auth-guard-usage.md
@@ -0,0 +1,319 @@
+# Auth Guard Hook 사용 가이드
+
+## 개요
+
+`useAuthGuard()` Hook은 보호된 페이지에 인증 검증과 브라우저 캐시 방지 기능을 제공합니다.
+
+## 기능
+
+1. **실시간 인증 확인**: 페이지 로드 시 서버에 인증 상태 확인
+2. **뒤로가기 보호**: 로그아웃 후 브라우저 뒤로가기 시 캐시된 페이지 접근 차단
+3. **자동 리다이렉트**: 인증 실패 시 자동으로 로그인 페이지로 이동
+
+## 사용 방법
+
+### 기본 사용
+
+보호가 필요한 모든 페이지에 Hook을 추가하세요:
+
+```tsx
+"use client";
+
+import { useAuthGuard } from '@/hooks/useAuthGuard';
+
+export default function ProtectedPage() {
+ // 🔒 인증 보호 및 브라우저 캐시 방지
+ useAuthGuard();
+
+ return (
+
+ {/* 보호된 컨텐츠 */}
+
+ );
+}
+```
+
+### 적용 예시
+
+#### Dashboard 페이지
+```tsx
+// src/app/[locale]/dashboard/page.tsx
+"use client";
+
+import { useAuthGuard } from '@/hooks/useAuthGuard';
+
+export default function Dashboard() {
+ useAuthGuard(); // 한 줄만 추가하면 끝!
+
+ return
Dashboard Content
;
+}
+```
+
+#### Profile 페이지
+```tsx
+// src/app/[locale]/profile/page.tsx
+"use client";
+
+import { useAuthGuard } from '@/hooks/useAuthGuard';
+
+export default function Profile() {
+ useAuthGuard();
+
+ return
Profile Content
;
+}
+```
+
+#### Settings 페이지
+```tsx
+// src/app/[locale]/settings/page.tsx
+"use client";
+
+import { useAuthGuard } from '@/hooks/useAuthGuard';
+
+export default function Settings() {
+ useAuthGuard();
+
+ return
Settings Content
;
+}
+```
+
+## 적용이 필요한 페이지
+
+다음 페이지들에 `useAuthGuard()` Hook을 적용해야 합니다:
+
+### 필수 적용 페이지
+- ✅ `/dashboard` - 이미 적용됨
+- ⏳ `/profile` - 적용 필요
+- ⏳ `/settings` - 적용 필요
+- ⏳ `/admin/*` - 모든 관리자 페이지
+- ⏳ `/tenant/*` - 모든 테넌트 관리 페이지
+- ⏳ `/users/*` - 사용자 관리 페이지
+- ⏳ `/reports/*` - 리포트 페이지
+- ⏳ `/analytics/*` - 분석 페이지
+- ⏳ `/inventory/*` - 재고 관리 페이지
+- ⏳ `/finance/*` - 재무 관리 페이지
+- ⏳ `/hr/*` - 인사 관리 페이지
+- ⏳ `/crm/*` - CRM 페이지
+
+### 적용 불필요 페이지
+- ❌ `/login` - 게스트 전용
+- ❌ `/signup` - 게스트 전용
+- ❌ `/forgot-password` - 게스트 전용
+
+## 동작 방식
+
+### 1. 페이지 로드 시
+```
+페이지 컴포넌트 마운트
+ ↓
+useAuthGuard() 실행
+ ↓
+/api/auth/check 호출 (HttpOnly 쿠키 검증)
+ ↓
+인증 성공 → 페이지 표시
+인증 실패 → /login으로 리다이렉트
+```
+
+### 2. 뒤로가기 시 (브라우저 캐시)
+```
+브라우저 뒤로가기
+ ↓
+pageshow 이벤트 감지
+ ↓
+event.persisted === true? (캐시된 페이지인가?)
+ ↓
+Yes → window.location.reload() (새로고침)
+ ↓
+useAuthGuard() 재실행
+ ↓
+인증 확인 → 쿠키 없음 → /login 리다이렉트
+```
+
+## 내부 구현
+
+`src/hooks/useAuthGuard.ts`:
+
+```typescript
+export function useAuthGuard() {
+ const router = useRouter();
+
+ useEffect(() => {
+ // 1. 인증 확인
+ const checkAuth = async () => {
+ const response = await fetch('/api/auth/check');
+ if (!response.ok) {
+ router.replace('/login');
+ }
+ };
+
+ checkAuth();
+
+ // 2. 브라우저 캐시 방지
+ const handlePageShow = (event: PageTransitionEvent) => {
+ if (event.persisted) {
+ window.location.reload();
+ }
+ };
+
+ window.addEventListener('pageshow', handlePageShow);
+
+ return () => {
+ window.removeEventListener('pageshow', handlePageShow);
+ };
+ }, [router]);
+}
+```
+
+## API 엔드포인트
+
+### GET /api/auth/check
+
+**목적**: HttpOnly 쿠키를 통한 인증 상태 확인
+
+**요청:**
+```http
+GET /api/auth/check HTTP/1.1
+Cookie: user_token=...
+```
+
+**응답 (인증 성공):**
+```json
+{
+ "authenticated": true
+}
+```
+Status: `200 OK`
+
+**응답 (인증 실패):**
+```json
+{
+ "error": "Not authenticated",
+ "authenticated": false
+}
+```
+Status: `401 Unauthorized`
+
+## 테스트 시나리오
+
+### 시나리오 1: 정상 접근
+1. 로그인 상태로 `/dashboard` 접근
+2. ✅ 페이지 정상 표시
+3. 콘솔 로그 없음 (정상 동작)
+
+### 시나리오 2: 비로그인 접근
+1. 로그아웃 상태로 `/dashboard` URL 직접 입력
+2. ✅ 즉시 `/login`으로 리다이렉트
+3. 콘솔: "⚠️ 인증 실패: 로그인 페이지로 이동"
+
+### 시나리오 3: 로그아웃 후 뒤로가기
+1. `/dashboard` 접속 (로그인 상태)
+2. Logout 버튼 클릭 → `/login` 이동
+3. 브라우저 뒤로가기 버튼 클릭
+4. ✅ 캐시된 페이지 감지 → 새로고침 → `/login` 리다이렉트
+5. 콘솔: "🔄 캐시된 페이지 감지: 새로고침"
+
+### 시나리오 4: 다른 탭에서 로그아웃
+1. 탭 A: `/dashboard` 접속 (로그인 상태)
+2. 탭 B: 같은 브라우저에서 로그아웃
+3. 탭 A: 페이지 새로고침 또는 다른 페이지 이동
+4. ✅ 인증 확인 실패 → `/login` 리다이렉트
+
+## Middleware와의 관계
+
+| 보안 레이어 | 역할 | 타이밍 |
+|-----------|------|--------|
+| **Middleware** | 서버 사이드 경로 보호 | 모든 요청 전 |
+| **useAuthGuard** | 클라이언트 사이드 보호 | 페이지 마운트 시 |
+
+### 왜 둘 다 필요한가?
+
+**Middleware만 있으면?**
+- ❌ 브라우저 뒤로가기 캐시 문제 해결 안됨
+- ❌ 실시간 인증 상태 변경 감지 안됨
+
+**useAuthGuard만 있으면?**
+- ❌ URL 직접 접근 시 보호 지연 (컴포넌트 마운트 후)
+- ❌ 서버 사이드 렌더링 보호 안됨
+
+**둘 다 있으면:**
+- ✅ 서버 + 클라이언트 이중 보호
+- ✅ 브라우저 캐시 문제 해결
+- ✅ 실시간 인증 상태 동기화
+
+## 성능 고려사항
+
+### API 호출 최소화
+- `useAuthGuard`는 페이지 마운트 시 1회만 호출
+- 페이지 이동 시마다 다시 실행됨 (의도된 동작)
+
+### 사용자 경험
+- 인증 확인은 비동기로 처리되어 UI 블로킹 없음
+- 인증 실패 시 `router.replace()` 사용 (뒤로가기 히스토리 오염 방지)
+
+## 문제 해결
+
+### 문제: Hook이 작동하지 않음
+**원인:** 페이지가 Server Component로 되어 있음
+**해결:** 파일 상단에 `"use client";` 추가
+
+### 문제: 무한 리다이렉트
+**원인:** `/login` 페이지에도 Hook 적용됨
+**해결:** 게스트 전용 페이지에는 Hook 사용 금지
+
+### 문제: 뒤로가기 시 여전히 페이지 보임
+**원인:** `pageshow` 이벤트 리스너 미등록
+**해결:** Hook이 올바르게 import되었는지 확인
+
+## 향후 개선 사항
+
+### 1. 토큰 검증 추가
+현재는 토큰 존재 여부만 확인하지만, 향후 PHP 백엔드에 토큰 유효성 검증 추가 가능:
+
+```typescript
+// /api/auth/check 개선
+const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/verify`, {
+ headers: { 'Authorization': `Bearer ${token}` }
+});
+```
+
+### 2. 자동 새로고침 주기
+장시간 페이지 유지 시 주기적 인증 확인:
+
+```typescript
+useEffect(() => {
+ const interval = setInterval(checkAuth, 5 * 60 * 1000); // 5분마다
+ return () => clearInterval(interval);
+}, []);
+```
+
+### 3. 세션 만료 경고
+토큰 만료 임박 시 사용자에게 알림:
+
+```typescript
+if (expiresIn < 5 * 60 * 1000) {
+ showToast('세션이 곧 만료됩니다. 다시 로그인해주세요.');
+}
+```
+
+## 요약
+
+✅ **적용 완료:**
+- Dashboard 페이지
+
+⏳ **적용 필요:**
+- 다른 모든 보호된 페이지들
+
+📝 **사용법:**
+```tsx
+import { useAuthGuard } from '@/hooks/useAuthGuard';
+
+export default function Page() {
+ useAuthGuard(); // 이 한 줄만 추가!
+ return
Content
;
+}
+```
+
+🔒 **보안 효과:**
+- 브라우저 캐시 악용 방지
+- 실시간 인증 상태 동기화
+- 로그아웃 후 완전한 페이지 접근 차단
diff --git a/docs/[IMPL-2025-11-07] authentication-implementation-guide.md b/docs/[IMPL-2025-11-07] authentication-implementation-guide.md
new file mode 100644
index 00000000..521365b8
--- /dev/null
+++ b/docs/[IMPL-2025-11-07] authentication-implementation-guide.md
@@ -0,0 +1,310 @@
+# 인증 시스템 구현 가이드
+
+## 📋 개요
+
+Laravel PHP 백엔드와 Next.js 15 프론트엔드 간의 3가지 인증 방식을 지원하는 통합 인증 시스템
+
+---
+
+## 🔐 지원 인증 방식
+
+### 1️⃣ Sanctum Session (웹 사용자)
+- **대상**: 웹 브라우저 사용자
+- **방식**: HTTP-only 쿠키 기반 세션
+- **보안**: XSS 방어 + CSRF 토큰
+- **Stateful**: Yes
+
+### 2️⃣ Bearer Token (모바일/SPA)
+- **대상**: 모바일 앱, 외부 SPA
+- **방식**: Authorization: Bearer {token}
+- **보안**: 토큰 만료 시간 관리
+- **Stateful**: No
+
+### 3️⃣ API Key (시스템 간 통신)
+- **대상**: 서버 간 통신, 백그라운드 작업
+- **방식**: X-API-KEY: {key}
+- **보안**: 서버 사이드 전용 (환경 변수)
+- **Stateful**: No
+
+---
+
+## 📁 파일 구조
+
+```
+src/
+├─ lib/api/
+│ ├─ client.ts # 통합 HTTP Client (3가지 인증 방식)
+│ │
+│ └─ auth/
+│ ├─ types.ts # 인증 타입 정의
+│ ├─ auth-config.ts # 인증 설정 (라우트, URL)
+│ │
+│ ├─ sanctum-client.ts # Sanctum 전용 클라이언트
+│ ├─ bearer-client.ts # Bearer 토큰 클라이언트
+│ ├─ api-key-client.ts # API Key 클라이언트
+│ │
+│ ├─ token-storage.ts # Bearer 토큰 저장 관리
+│ ├─ api-key-validator.ts # API Key 검증 유틸
+│ └─ server-auth.ts # 서버 컴포넌트 인증 유틸
+│
+├─ contexts/
+│ └─ AuthContext.tsx # 클라이언트 인증 상태 관리
+│
+├─ middleware.ts # 통합 미들웨어 (Bot + Auth + i18n)
+│
+└─ app/[locale]/
+ ├─ (auth)/
+ │ └─ login/page.tsx # 로그인 페이지
+ │
+ └─ (protected)/
+ └─ dashboard/page.tsx # 보호된 페이지
+```
+
+---
+
+## 🔧 환경 변수 설정
+
+### .env.local (실제 키 값)
+```env
+# API Configuration
+NEXT_PUBLIC_API_URL=https://api.5130.co.kr
+NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
+
+# Authentication Mode
+NEXT_PUBLIC_AUTH_MODE=sanctum
+
+# API Key (서버 사이드 전용 - 절대 공개 금지!)
+API_KEY=42Jfwc6EaRQ04GNRmLR5kzJp5UudSOzGGqjmdk1a
+```
+
+### .env.example (템플릿)
+```env
+NEXT_PUBLIC_API_URL=https://api.5130.co.kr
+NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
+NEXT_PUBLIC_AUTH_MODE=sanctum
+API_KEY=your-secret-api-key-here
+```
+
+---
+
+## 🎯 구현 단계
+
+### Phase 1: 핵심 인프라 (필수)
+1. `lib/api/auth/types.ts` - 타입 정의
+2. `lib/api/auth/auth-config.ts` - 인증 설정
+3. `lib/api/client.ts` - 통합 HTTP 클라이언트
+4. `lib/api/auth/sanctum-client.ts` - Sanctum 클라이언트
+
+### Phase 2: Middleware 통합
+1. `middleware.ts` 확장 - 인증 체크 로직 추가
+2. 라우트 보호 구현 (protected/guest-only)
+
+### Phase 3: 로그인 페이지
+1. `app/[locale]/(auth)/login/page.tsx`
+2. 기존 validation schema 활용
+
+### Phase 4: 보호된 페이지
+1. `app/[locale]/(protected)/dashboard/page.tsx`
+2. Server Component로 구현
+
+---
+
+## 🔒 보안 고려사항
+
+### 환경 변수 보안
+```yaml
+✅ NEXT_PUBLIC_*: 브라우저 노출 가능
+❌ API_KEY: 절대 NEXT_PUBLIC_ 붙이지 말 것!
+✅ .env.local은 .gitignore에 포함됨
+```
+
+### 인증 방식별 보안
+```yaml
+Sanctum:
+ ✅ HTTP-only 쿠키 (XSS 방어)
+ ✅ CSRF 토큰 자동 처리
+ ✅ Same-Site: Lax
+
+Bearer Token:
+ ⚠️ localStorage 사용 (XSS 취약)
+ ✅ 토큰 만료 시간 체크
+ ✅ Refresh token 권장
+
+API Key:
+ ⚠️ 서버 사이드 전용
+ ✅ 환경 변수 관리
+ ✅ 주기적 갱신 대비
+```
+
+---
+
+## 📊 Middleware 인증 플로우
+
+```
+Request
+ ↓
+1. Bot Detection (기존)
+ ├─ Bot → 403 Forbidden
+ └─ Human → Continue
+ ↓
+2. Static Files Check
+ ├─ Static → Skip Auth
+ └─ Dynamic → Continue
+ ↓
+3. Public Routes Check
+ ├─ Public → Skip Auth
+ └─ Protected → Continue
+ ↓
+4. Authentication Check
+ ├─ Sanctum Session Cookie
+ ├─ Bearer Token (Authorization header)
+ └─ API Key (X-API-KEY header)
+ ↓
+5. Protected Routes Guard
+ ├─ Authenticated → Allow
+ └─ Not Authenticated → Redirect /login
+ ↓
+6. Guest Only Routes
+ ├─ Authenticated → Redirect /dashboard
+ └─ Not Authenticated → Allow
+ ↓
+7. i18n Routing
+ ↓
+Response
+```
+
+---
+
+## 🚀 API 엔드포인트
+
+### 로그인
+```
+POST /api/v1/login
+Content-Type: application/json
+
+Request:
+{
+ "user_id": "hamss",
+ "user_pwd": "StrongPass!1234"
+}
+
+Response (성공):
+{
+ "user": {
+ "id": 1,
+ "name": "홍길동",
+ "email": "hamss@example.com"
+ },
+ "message": "로그인 성공"
+}
+
+Cookie: laravel_session=xxx; HttpOnly; SameSite=Lax
+```
+
+### 로그아웃
+```
+POST /api/v1/logout
+
+Response:
+{
+ "message": "로그아웃 성공"
+}
+```
+
+### 현재 사용자 정보
+```
+GET /api/user
+Cookie: laravel_session=xxx
+
+Response:
+{
+ "id": 1,
+ "name": "홍길동",
+ "email": "hamss@example.com"
+}
+```
+
+---
+
+## 📝 사용 예시
+
+### 1. Sanctum 로그인 (웹 사용자)
+```typescript
+import { sanctumClient } from '@/lib/api/auth/sanctum-client';
+
+const user = await sanctumClient.login({
+ user_id: 'hamss',
+ user_pwd: 'StrongPass!1234'
+});
+```
+
+### 2. API Key 요청 (서버 사이드)
+```typescript
+import { createApiKeyClient } from '@/lib/api/auth/api-key-client';
+
+const client = createApiKeyClient();
+const data = await client.fetchData('/api/external-data');
+```
+
+### 3. Bearer Token 로그인 (모바일)
+```typescript
+import { bearerClient } from '@/lib/api/auth/bearer-client';
+
+const user = await bearerClient.login({
+ email: 'user@example.com',
+ password: 'password'
+});
+```
+
+---
+
+## ⚠️ 주의사항
+
+### API Key 갱신
+- PHP 팀에서 주기적으로 새 키 발급
+- `.env.local`의 `API_KEY` 값만 변경
+- 코드 수정 불필요, 서버 재시작만 필요
+
+### Git 보안
+- `.env.local`은 절대 커밋 금지
+- `.env.example`만 템플릿으로 커밋
+- `.gitignore`에 `.env.local` 포함 확인
+
+### 개발 환경
+- 개발 서버 시작 시 API Key 자동 검증
+- 콘솔에 검증 상태 출력
+- 에러 발생 시 명확한 가이드 제공
+
+---
+
+## 🔍 트러블슈팅
+
+### API Key 에러
+```
+❌ API_KEY is not configured!
+📝 Please check:
+ 1. .env.local file exists
+ 2. API_KEY is set correctly
+ 3. Restart development server (npm run dev)
+
+💡 Contact backend team if you need a new API key.
+```
+
+### CORS 에러
+- Laravel `config/cors.php` 확인
+- `supports_credentials: true` 설정
+- `allowed_origins`에 Next.js URL 포함
+
+### 세션 쿠키 안받아짐
+- Laravel `SANCTUM_STATEFUL_DOMAINS` 확인
+- `localhost:3000` 포함 확인
+- `SESSION_DOMAIN` 설정 확인
+
+---
+
+## 📚 참고 문서
+
+- [Laravel Sanctum 공식 문서](https://laravel.com/docs/sanctum)
+- [Next.js Middleware 문서](https://nextjs.org/docs/app/building-your-application/routing/middleware)
+- [claudedocs/authentication-design.md](./authentication-design.md)
+- [claudedocs/api-requirements.md](./api-requirements.md)
\ No newline at end of file
diff --git a/docs/[IMPL-2025-11-07] form-validation-guide.md b/docs/[IMPL-2025-11-07] form-validation-guide.md
new file mode 100644
index 00000000..ebeca903
--- /dev/null
+++ b/docs/[IMPL-2025-11-07] form-validation-guide.md
@@ -0,0 +1,1020 @@
+# 폼 및 유효성 검증 가이드
+
+## 📋 문서 개요
+
+이 문서는 React Hook Form과 Zod를 사용하여 타입 안전하고 다국어를 지원하는 폼 컴포넌트를 구현하는 방법을 설명합니다.
+
+**작성일**: 2025-11-06
+**프로젝트**: Multi-tenant ERP System
+**기술 스택**:
+- React Hook Form: 7.54.2
+- Zod: 3.24.1
+- @hookform/resolvers: 3.9.1
+- next-intl: 4.4.0
+
+---
+
+## 🎯 왜 React Hook Form + Zod인가?
+
+### React Hook Form의 장점
+- ✅ **성능 최적화**: 비제어 컴포넌트 기반으로 리렌더링 최소화
+- ✅ **TypeScript 완벽 지원**: 타입 안전성 보장
+- ✅ **작은 번들 크기**: ~8KB (gzipped)
+- ✅ **간단한 API**: 직관적이고 배우기 쉬움
+- ✅ **유연한 검증**: 다양한 검증 라이브러리 지원
+
+### Zod의 장점
+- ✅ **스키마 우선 검증**: 명확하고 재사용 가능한 검증 로직
+- ✅ **TypeScript 타입 추론**: 스키마에서 자동으로 타입 생성
+- ✅ **런타임 검증**: 컴파일 타임 + 런타임 안전성
+- ✅ **체이닝 가능**: 읽기 쉽고 확장 가능한 검증 규칙
+- ✅ **커스텀 에러 메시지**: 다국어 에러 메시지 완벽 지원
+
+---
+
+## 📦 설치된 패키지
+
+```json
+{
+ "dependencies": {
+ "react-hook-form": "^7.54.2",
+ "zod": "^3.24.1",
+ "@hookform/resolvers": "^3.9.1"
+ }
+}
+```
+
+**@hookform/resolvers**: React Hook Form과 Zod를 연결하는 어댑터
+
+---
+
+## 🚀 기본 사용법
+
+### 1. Zod 스키마 정의
+
+```typescript
+// src/lib/validation/auth.schema.ts
+import { z } from 'zod';
+
+export const loginSchema = z.object({
+ email: z
+ .string()
+ .min(1, 'validation.email.required')
+ .email('validation.email.invalid'),
+ password: z
+ .string()
+ .min(8, 'validation.password.min')
+ .max(100, 'validation.password.max'),
+ rememberMe: z.boolean().optional(),
+});
+
+// TypeScript 타입 자동 추론
+export type LoginFormData = z.infer;
+```
+
+### 2. React Hook Form 통합
+
+```typescript
+// src/components/LoginForm.tsx
+'use client';
+
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useTranslations } from 'next-intl';
+import { loginSchema, type LoginFormData } from '@/lib/validation/auth.schema';
+
+export default function LoginForm() {
+ const t = useTranslations('auth');
+ const tValidation = useTranslations('validation');
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ resolver: zodResolver(loginSchema),
+ defaultValues: {
+ email: '',
+ password: '',
+ rememberMe: false,
+ },
+ });
+
+ const onSubmit = async (data: LoginFormData) => {
+ try {
+ // Laravel API 호출
+ const response = await fetch('/api/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data),
+ });
+
+ if (!response.ok) throw new Error('Login failed');
+
+ const result = await response.json();
+ // 로그인 성공 처리
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ return (
+
+ );
+}
+```
+
+---
+
+## 🌐 next-intl 통합
+
+### 1. 검증 메시지 번역 파일 추가
+
+```json
+// src/messages/ko.json
+{
+ "validation": {
+ "email": {
+ "required": "이메일을 입력해주세요",
+ "invalid": "유효한 이메일 주소를 입력해주세요"
+ },
+ "password": {
+ "required": "비밀번호를 입력해주세요",
+ "min": "비밀번호는 최소 {min}자 이상이어야 합니다",
+ "max": "비밀번호는 최대 {max}자 이하여야 합니다"
+ },
+ "name": {
+ "required": "이름을 입력해주세요",
+ "min": "이름은 최소 {min}자 이상이어야 합니다"
+ },
+ "phone": {
+ "invalid": "유효한 전화번호를 입력해주세요"
+ },
+ "required": "필수 입력 항목입니다"
+ }
+}
+```
+
+```json
+// src/messages/en.json
+{
+ "validation": {
+ "email": {
+ "required": "Email is required",
+ "invalid": "Please enter a valid email address"
+ },
+ "password": {
+ "required": "Password is required",
+ "min": "Password must be at least {min} characters",
+ "max": "Password must be at most {max} characters"
+ },
+ "name": {
+ "required": "Name is required",
+ "min": "Name must be at least {min} characters"
+ },
+ "phone": {
+ "invalid": "Please enter a valid phone number"
+ },
+ "required": "This field is required"
+ }
+}
+```
+
+```json
+// src/messages/ja.json
+{
+ "validation": {
+ "email": {
+ "required": "メールアドレスを入力してください",
+ "invalid": "有効なメールアドレスを入力してください"
+ },
+ "password": {
+ "required": "パスワードを入力してください",
+ "min": "パスワードは{min}文字以上である必要があります",
+ "max": "パスワードは{max}文字以下である必要があります"
+ },
+ "name": {
+ "required": "名前を入力してください",
+ "min": "名前は{min}文字以上である必要があります"
+ },
+ "phone": {
+ "invalid": "有効な電話番号を入力してください"
+ },
+ "required": "この項目は必須です"
+ }
+}
+```
+
+### 2. 다국어 에러 메시지 표시 유틸리티
+
+```typescript
+// src/lib/utils/form-error.ts
+import { FieldError } from 'react-hook-form';
+
+export function getErrorMessage(
+ error: FieldError | undefined,
+ t: (key: string, values?: Record) => string
+): string | undefined {
+ if (!error) return undefined;
+
+ // 에러 메시지가 번역 키인 경우
+ if (typeof error.message === 'string' && error.message.startsWith('validation.')) {
+ return t(error.message);
+ }
+
+ // 직접 에러 메시지인 경우
+ return error.message;
+}
+```
+
+---
+
+## 💼 ERP 실전 예제
+
+### 1. 제품 등록 폼
+
+```typescript
+// src/lib/validation/product.schema.ts
+import { z } from 'zod';
+
+export const productSchema = z.object({
+ sku: z
+ .string()
+ .min(1, 'validation.required')
+ .regex(/^[A-Z0-9-]+$/, 'validation.sku.format'),
+ name: z.object({
+ ko: z.string().min(1, 'validation.required'),
+ en: z.string().min(1, 'validation.required'),
+ ja: z.string().optional(),
+ }),
+ description: z.object({
+ ko: z.string().optional(),
+ en: z.string().optional(),
+ ja: z.string().optional(),
+ }),
+ price: z
+ .number()
+ .min(0, 'validation.price.min')
+ .max(999999999, 'validation.price.max'),
+ stock: z
+ .number()
+ .int('validation.stock.int')
+ .min(0, 'validation.stock.min'),
+ category: z.string().min(1, 'validation.required'),
+ isActive: z.boolean().default(true),
+});
+
+export type ProductFormData = z.infer;
+```
+
+```typescript
+// src/components/ProductForm.tsx
+'use client';
+
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useTranslations, useLocale } from 'next-intl';
+import { productSchema, type ProductFormData } from '@/lib/validation/product.schema';
+
+export default function ProductForm() {
+ const t = useTranslations('product');
+ const tValidation = useTranslations('validation');
+ const locale = useLocale();
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ resolver: zodResolver(productSchema),
+ });
+
+ const onSubmit = async (data: ProductFormData) => {
+ try {
+ const response = await fetch(`${process.env.NEXT_PUBLIC_LARAVEL_API_URL}/api/products`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${getAuthToken()}`,
+ 'X-Locale': locale,
+ },
+ body: JSON.stringify(data),
+ });
+
+ if (!response.ok) throw new Error('Failed to create product');
+
+ const result = await response.json();
+ // 성공 처리
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ return (
+
+ );
+}
+```
+
+### 2. 고급 검증: 조건부 필드
+
+```typescript
+// src/lib/validation/employee.schema.ts
+import { z } from 'zod';
+
+export const employeeSchema = z
+ .object({
+ name: z.string().min(1, 'validation.required'),
+ email: z.string().email('validation.email.invalid'),
+ department: z.string().min(1, 'validation.required'),
+ position: z.string().min(1, 'validation.required'),
+ employmentType: z.enum(['full-time', 'part-time', 'contract']),
+
+ // 계약직인 경우 계약 종료일 필수
+ contractEndDate: z.string().optional(),
+
+ // 관리자인 경우 승인 권한 레벨 필수
+ isManager: z.boolean().default(false),
+ approvalLevel: z.number().min(1).max(5).optional(),
+ })
+ .refine(
+ (data) => {
+ // 계약직인 경우 계약 종료일 필수
+ if (data.employmentType === 'contract') {
+ return !!data.contractEndDate;
+ }
+ return true;
+ },
+ {
+ message: 'validation.contractEndDate.required',
+ path: ['contractEndDate'],
+ }
+ )
+ .refine(
+ (data) => {
+ // 관리자인 경우 승인 권한 레벨 필수
+ if (data.isManager) {
+ return data.approvalLevel !== undefined;
+ }
+ return true;
+ },
+ {
+ message: 'validation.approvalLevel.required',
+ path: ['approvalLevel'],
+ }
+ );
+
+export type EmployeeFormData = z.infer;
+```
+
+---
+
+## 🎨 재사용 가능한 폼 컴포넌트
+
+### 1. Input Field 컴포넌트
+
+```typescript
+// src/components/form/FormInput.tsx
+import { UseFormRegister, FieldError } from 'react-hook-form';
+import { useTranslations } from 'next-intl';
+
+interface FormInputProps {
+ name: string;
+ label: string;
+ type?: 'text' | 'email' | 'password' | 'number' | 'tel';
+ placeholder?: string;
+ required?: boolean;
+ register: UseFormRegister;
+ error?: FieldError;
+ className?: string;
+}
+
+export default function FormInput({
+ name,
+ label,
+ type = 'text',
+ placeholder,
+ required = false,
+ register,
+ error,
+ className = '',
+}: FormInputProps) {
+ const tValidation = useTranslations('validation');
+
+ return (
+
+ );
+}
+```
+
+### 3. 간단한 폼 사용 예제
+
+```typescript
+// src/components/SimpleLoginForm.tsx
+'use client';
+
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { loginSchema, type LoginFormData } from '@/lib/validation/auth.schema';
+import FormInput from '@/components/form/FormInput';
+
+export default function SimpleLoginForm() {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ resolver: zodResolver(loginSchema),
+ });
+
+ const onSubmit = async (data: LoginFormData) => {
+ console.log(data);
+ };
+
+ return (
+
+ );
+}
+```
+
+---
+
+## ✅ Best Practices
+
+### 1. 스키마 구조화
+
+```typescript
+// src/lib/validation/schemas/
+// ├── auth.schema.ts # 인증 관련
+// ├── product.schema.ts # 제품 관련
+// ├── employee.schema.ts # 직원 관련
+// ├── order.schema.ts # 주문 관련
+// └── common.schema.ts # 공통 스키마
+
+// src/lib/validation/schemas/common.schema.ts
+import { z } from 'zod';
+
+// 재사용 가능한 공통 스키마
+export const emailSchema = z.string().email('validation.email.invalid');
+
+export const phoneSchema = z
+ .string()
+ .regex(/^01[0-9]-[0-9]{4}-[0-9]{4}$/, 'validation.phone.invalid');
+
+export const passwordSchema = z
+ .string()
+ .min(8, 'validation.password.min')
+ .max(100, 'validation.password.max')
+ .regex(/[a-z]/, 'validation.password.lowercase')
+ .regex(/[A-Z]/, 'validation.password.uppercase')
+ .regex(/[0-9]/, 'validation.password.number');
+```
+
+### 2. 타입 안전성 보장
+
+```typescript
+// 스키마에서 타입 추론
+export type LoginFormData = z.infer;
+
+// API 응답 타입도 Zod로 정의
+export const loginResponseSchema = z.object({
+ user: z.object({
+ id: z.string().uuid(),
+ email: z.string().email(),
+ name: z.string(),
+ }),
+ token: z.string(),
+});
+
+export type LoginResponse = z.infer;
+```
+
+### 3. 에러 처리 패턴
+
+```typescript
+// src/lib/utils/form-error-handler.ts
+import { ZodError } from 'zod';
+import { FieldErrors, UseFormSetError } from 'react-hook-form';
+
+export function handleZodError(
+ error: ZodError,
+ setError: UseFormSetError
+) {
+ error.errors.forEach((err) => {
+ const path = err.path.join('.') as any;
+ setError(path, {
+ type: 'manual',
+ message: err.message,
+ });
+ });
+}
+
+// API 에러를 폼 에러로 변환
+export function handleApiError(
+ apiError: any,
+ setError: UseFormSetError
+) {
+ if (apiError.errors) {
+ Object.entries(apiError.errors).forEach(([field, messages]) => {
+ setError(field as any, {
+ type: 'manual',
+ message: (messages as string[])[0],
+ });
+ });
+ }
+}
+```
+
+### 4. 폼 상태 관리
+
+```typescript
+'use client';
+
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useEffect } from 'react';
+
+export default function EditProductForm({ productId }: { productId: string }) {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting, isDirty, isValid },
+ reset,
+ watch,
+ } = useForm({
+ resolver: zodResolver(productSchema),
+ mode: 'onChange', // 실시간 검증
+ });
+
+ // 초기 데이터 로드
+ useEffect(() => {
+ async function loadProduct() {
+ const response = await fetch(`/api/products/${productId}`);
+ const product = await response.json();
+ reset(product); // 폼 초기화
+ }
+ loadProduct();
+ }, [productId, reset]);
+
+ // 필드 값 감시
+ const price = watch('price');
+ const stock = watch('stock');
+
+ return (
+
+ );
+}
+```
+
+---
+
+## 🔒 보안 고려사항
+
+### 1. XSS 방지
+
+```typescript
+// Zod로 HTML 태그 제거
+export const safeTextSchema = z
+ .string()
+ .transform((val) => val.replace(/<[^>]*>/g, ''));
+
+// 또는 명시적으로 검증
+export const noHtmlSchema = z
+ .string()
+ .refine((val) => !/<[^>]*>/.test(val), {
+ message: 'validation.noHtml',
+ });
+```
+
+### 2. 파일 업로드 검증
+
+```typescript
+export const fileUploadSchema = z.object({
+ file: z
+ .instanceof(File)
+ .refine((file) => file.size <= 5 * 1024 * 1024, {
+ message: 'validation.file.maxSize', // 5MB
+ })
+ .refine(
+ (file) => ['image/jpeg', 'image/png', 'image/webp'].includes(file.type),
+ {
+ message: 'validation.file.type',
+ }
+ ),
+});
+```
+
+---
+
+## 📊 성능 최적화
+
+### 1. 검증 모드 선택
+
+```typescript
+useForm({
+ mode: 'onBlur', // 포커스를 잃을 때만 검증 (기본값)
+ mode: 'onChange', // 입력할 때마다 검증 (실시간)
+ mode: 'onSubmit', // 제출할 때만 검증 (가장 빠름)
+ mode: 'onTouched', // 필드를 터치한 후 변경될 때마다 검증
+});
+```
+
+### 2. 조건부 필드 렌더링
+
+```typescript
+const employmentType = watch('employmentType');
+
+return (
+
+);
+```
+
+---
+
+## 🧪 테스트
+
+### 1. Zod 스키마 테스트
+
+```typescript
+// __tests__/validation/auth.schema.test.ts
+import { describe, it, expect } from '@jest/globals';
+import { loginSchema } from '@/lib/validation/auth.schema';
+
+describe('loginSchema', () => {
+ it('should validate correct login data', () => {
+ const validData = {
+ email: 'user@example.com',
+ password: 'SecurePass123',
+ };
+
+ const result = loginSchema.safeParse(validData);
+ expect(result.success).toBe(true);
+ });
+
+ it('should reject invalid email', () => {
+ const invalidData = {
+ email: 'invalid-email',
+ password: 'SecurePass123',
+ };
+
+ const result = loginSchema.safeParse(invalidData);
+ expect(result.success).toBe(false);
+ });
+
+ it('should reject short password', () => {
+ const invalidData = {
+ email: 'user@example.com',
+ password: 'short',
+ };
+
+ const result = loginSchema.safeParse(invalidData);
+ expect(result.success).toBe(false);
+ });
+});
+```
+
+---
+
+## 📚 참고 자료
+
+- [React Hook Form 공식 문서](https://react-hook-form.com/)
+- [Zod 공식 문서](https://zod.dev/)
+- [next-intl 공식 문서](https://next-intl-docs.vercel.app/)
+- [@hookform/resolvers](https://github.com/react-hook-form/resolvers)
+
+---
+
+**문서 유효기간**: 2025-11-06 ~
+**다음 업데이트**: 새로운 폼 패턴 추가 시
+
+**작성자**: Claude Code
\ No newline at end of file
diff --git a/docs/[IMPL-2025-11-07] jwt-cookie-authentication-final.md b/docs/[IMPL-2025-11-07] jwt-cookie-authentication-final.md
new file mode 100644
index 00000000..7e2ccb0c
--- /dev/null
+++ b/docs/[IMPL-2025-11-07] jwt-cookie-authentication-final.md
@@ -0,0 +1,491 @@
+# JWT + Cookie + Middleware 인증 설계 (최종)
+
+**확정된 API 정보:**
+- 인증 방식: Bearer Token (JWT)
+- 로그인: `POST /api/v1/login`
+- 응답: `{ token: "xxx" }`
+- Token 저장: **쿠키** (Middleware 접근 가능)
+
+## ✅ 핵심 발견
+
+**JWT도 쿠키에 저장하면 Middleware에서 처리 가능합니다!**
+
+```typescript
+// middleware.ts에서 JWT 토큰 쿠키 접근
+const authToken = request.cookies.get('auth_token'); // ✅ 가능!
+
+if (!authToken) {
+ redirect('/login');
+}
+```
+
+따라서 **기존 Middleware 설계를 거의 그대로 사용**할 수 있습니다.
+
+---
+
+## 📋 아키텍처 (기존과 동일)
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Next.js Frontend │
+├─────────────────────────────────────────────────────────────┤
+│ Middleware (Server) │
+│ ├─ Bot Detection (기존) │
+│ ├─ Authentication Check (신규) │
+│ │ ├─ JWT Token 쿠키 확인 │
+│ │ └─ 없으면 /login 리다이렉트 │
+│ └─ i18n Routing (기존) │
+├─────────────────────────────────────────────────────────────┤
+│ JWT Client (lib/auth/jwt-client.ts) │
+│ ├─ Token을 쿠키에 저장 │
+│ ├─ API 호출 시 Authorization 헤더 추가 │
+│ └─ 401 응답 시 자동 로그아웃 │
+├─────────────────────────────────────────────────────────────┤
+│ Auth Context (contexts/AuthContext.tsx) │
+│ ├─ 사용자 정보 관리 │
+│ └─ login/logout 함수 │
+└─────────────────────────────────────────────────────────────┘
+ ↓ HTTP + Cookie + Authorization
+┌─────────────────────────────────────────────────────────────┐
+│ Laravel Backend │
+├─────────────────────────────────────────────────────────────┤
+│ JWT Middleware │
+│ └─ Bearer Token 검증 │
+├─────────────────────────────────────────────────────────────┤
+│ API Endpoints │
+│ ├─ POST /api/v1/login → { token: "xxx" } │
+│ ├─ POST /api/v1/register │
+│ ├─ GET /api/v1/user │
+│ └─ POST /api/v1/logout │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 🔐 인증 플로우
+
+### 1. 로그인
+
+```
+1. POST /api/v1/login
+ → { token: "eyJhbGci..." }
+
+2. Token을 쿠키에 저장
+ document.cookie = 'auth_token=xxx; Secure; SameSite=Strict'
+
+3. /dashboard 리다이렉트
+
+4. Middleware가 쿠키 확인 ✓
+
+5. 페이지 렌더링
+```
+
+### 2. API 호출
+
+```
+1. 쿠키에서 Token 읽기
+2. Authorization 헤더에 추가
+ Authorization: Bearer xxx
+3. Laravel이 JWT 검증
+4. 데이터 반환
+```
+
+### 3. 보호된 페이지 접근
+
+```
+사용자 → /dashboard
+ ↓
+Middleware 실행
+ ↓
+auth_token 쿠키 확인
+ ↓
+있음 → 페이지 표시
+없음 → /login 리다이렉트
+```
+
+---
+
+## 🛠️ 핵심 구현
+
+### 1. Token 저장 (lib/auth/token-storage.ts)
+
+```typescript
+export const tokenStorage = {
+ /**
+ * JWT를 쿠키에 저장
+ * - Middleware에서 접근 가능
+ * - Secure + SameSite로 보안 강화
+ */
+ set(token: string): void {
+ const maxAge = 86400; // 24시간
+ document.cookie = `auth_token=${token}; path=/; max-age=${maxAge}; SameSite=Strict; Secure`;
+ },
+
+ /**
+ * 쿠키에서 Token 읽기
+ * - 클라이언트에서만 사용
+ */
+ get(): string | null {
+ if (typeof window === 'undefined') return null;
+
+ const match = document.cookie.match(/auth_token=([^;]+)/);
+ return match ? match[1] : null;
+ },
+
+ /**
+ * Token 삭제
+ */
+ remove(): void {
+ document.cookie = 'auth_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
+ }
+};
+```
+
+### 2. JWT Client (lib/auth/jwt-client.ts)
+
+```typescript
+import { tokenStorage } from './token-storage';
+
+class JwtClient {
+ private baseURL = 'https://api.5130.co.kr';
+
+ /**
+ * 로그인
+ */
+ async login(email: string, password: string): Promise {
+ const response = await fetch(`${this.baseURL}/api/v1/login`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password }),
+ });
+
+ if (!response.ok) {
+ throw new Error('Login failed');
+ }
+
+ const { token } = await response.json();
+
+ // ✅ Token을 쿠키에 저장
+ tokenStorage.set(token);
+
+ // 사용자 정보 조회
+ return await this.getCurrentUser();
+ }
+
+ /**
+ * 현재 사용자 정보
+ */
+ async getCurrentUser(): Promise {
+ const token = tokenStorage.get();
+
+ if (!token) {
+ throw new Error('No token');
+ }
+
+ const response = await fetch(`${this.baseURL}/api/v1/user`, {
+ headers: {
+ 'Authorization': `Bearer ${token}`, // ✅ Authorization 헤더
+ },
+ });
+
+ if (response.status === 401) {
+ tokenStorage.remove();
+ throw new Error('Unauthorized');
+ }
+
+ return await response.json();
+ }
+
+ /**
+ * 로그아웃
+ */
+ async logout(): Promise {
+ const token = tokenStorage.get();
+
+ if (token) {
+ await fetch(`${this.baseURL}/api/v1/logout`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ },
+ });
+ }
+
+ // ✅ 쿠키 삭제
+ tokenStorage.remove();
+ }
+}
+
+export const jwtClient = new JwtClient();
+```
+
+### 3. Middleware (middleware.ts) - 기존과 거의 동일!
+
+```typescript
+import { NextResponse } from 'next/server';
+import type { NextRequest } from 'next/server';
+import createIntlMiddleware from 'next-intl/middleware';
+import { locales, defaultLocale } from '@/i18n/config';
+
+const intlMiddleware = createIntlMiddleware({
+ locales,
+ defaultLocale,
+ localePrefix: 'as-needed',
+});
+
+// 보호된 라우트
+const PROTECTED_ROUTES = [
+ '/dashboard',
+ '/profile',
+ '/settings',
+ '/admin',
+ '/tenant',
+ '/users',
+ '/reports',
+];
+
+// 공개 라우트
+const PUBLIC_ROUTES = [
+ '/',
+ '/login',
+ '/register',
+ '/about',
+ '/contact',
+];
+
+function isProtectedRoute(pathname: string): boolean {
+ return PROTECTED_ROUTES.some(route => pathname.startsWith(route));
+}
+
+function isPublicRoute(pathname: string): boolean {
+ return PUBLIC_ROUTES.some(route => pathname === route || pathname.startsWith(route));
+}
+
+function stripLocale(pathname: string): string {
+ for (const locale of locales) {
+ if (pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`) {
+ return pathname.slice(`/${locale}`.length) || '/';
+ }
+ }
+ return pathname;
+}
+
+export function middleware(request: NextRequest) {
+ const { pathname } = request.nextUrl;
+
+ // 1. Bot Detection (기존 로직)
+ // ... bot check code ...
+
+ // 2. 정적 파일 제외
+ if (
+ pathname.includes('/_next/') ||
+ pathname.includes('/api/') ||
+ pathname.match(/\.(ico|png|jpg|jpeg|svg|gif|webp)$/)
+ ) {
+ return intlMiddleware(request);
+ }
+
+ // 3. 로케일 제거
+ const pathnameWithoutLocale = stripLocale(pathname);
+
+ // 4. ✅ JWT Token 쿠키 확인
+ const authToken = request.cookies.get('auth_token');
+ const isAuthenticated = !!authToken;
+
+ // 5. 보호된 라우트 체크
+ if (isProtectedRoute(pathnameWithoutLocale) && !isAuthenticated) {
+ const url = new URL('/login', request.url);
+ url.searchParams.set('redirect', pathname);
+ return NextResponse.redirect(url);
+ }
+
+ // 6. 게스트 전용 라우트 (이미 로그인한 경우)
+ if (
+ (pathnameWithoutLocale === '/login' || pathnameWithoutLocale === '/register') &&
+ isAuthenticated
+ ) {
+ return NextResponse.redirect(new URL('/dashboard', request.url));
+ }
+
+ // 7. i18n 미들웨어
+ return intlMiddleware(request);
+}
+
+export const config = {
+ matcher: [
+ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)',
+ ],
+};
+```
+
+**변경 사항:**
+```diff
+- const sessionCookie = request.cookies.get('laravel_session');
++ const authToken = request.cookies.get('auth_token');
+```
+
+거의 동일합니다!
+
+### 4. Auth Context (contexts/AuthContext.tsx)
+
+```typescript
+'use client';
+
+import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
+import { jwtClient } from '@/lib/auth/jwt-client';
+import { useRouter } from 'next/navigation';
+
+interface User {
+ id: number;
+ name: string;
+ email: string;
+}
+
+interface AuthContextType {
+ user: User | null;
+ loading: boolean;
+ login: (email: string, password: string) => Promise;
+ logout: () => Promise;
+}
+
+const AuthContext = createContext(undefined);
+
+export function AuthProvider({ children }: { children: ReactNode }) {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const router = useRouter();
+
+ // 초기 로드 시 사용자 정보 가져오기
+ useEffect(() => {
+ jwtClient.getCurrentUser()
+ .then(setUser)
+ .catch(() => setUser(null))
+ .finally(() => setLoading(false));
+ }, []);
+
+ const login = async (email: string, password: string) => {
+ const user = await jwtClient.login(email, password);
+ setUser(user);
+ router.push('/dashboard');
+ };
+
+ const logout = async () => {
+ await jwtClient.logout();
+ setUser(null);
+ router.push('/login');
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useAuth() {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within AuthProvider');
+ }
+ return context;
+}
+```
+
+---
+
+## 📊 세션 쿠키 vs JWT 쿠키 비교
+
+| 항목 | 세션 쿠키 (Sanctum) | JWT 쿠키 (현재) |
+|------|---------------------|------------------|
+| **쿠키 이름** | `laravel_session` | `auth_token` |
+| **Middleware 접근** | ✅ 가능 | ✅ 가능 |
+| **인증 체크** | 쿠키 존재 확인 | 쿠키 존재 확인 |
+| **API 호출** | 쿠키 자동 포함 | Authorization 헤더 |
+| **CSRF 토큰** | ✅ 필요 | ❌ 불필요 |
+| **서버 상태** | Stateful (세션 저장) | Stateless |
+| **보안** | HTTP-only 가능 | Secure + SameSite |
+| **구현 복잡도** | 동일 | 동일 |
+
+**결론:** Middleware 관점에서는 거의 동일합니다!
+
+---
+
+## 🎯 구현 순서
+
+### Phase 1: 기본 인프라 (30분)
+- [x] auth-config.ts
+- [ ] token-storage.ts
+- [ ] jwt-client.ts
+- [ ] types/auth.ts
+
+### Phase 2: Middleware 통합 (20분)
+- [ ] middleware.ts 업데이트
+ - JWT 토큰 쿠키 체크
+ - Protected routes 가드
+
+### Phase 3: Auth Context (20분)
+- [ ] AuthContext.tsx
+- [ ] layout.tsx에 AuthProvider 추가
+
+### Phase 4: 로그인 페이지 (40분)
+- [ ] /login/page.tsx
+- [ ] LoginForm 컴포넌트
+- [ ] Form validation (react-hook-form + zod)
+
+### Phase 5: 테스트 (30분)
+- [ ] 로그인 → 대시보드
+- [ ] 비로그인 → 대시보드 → /login 튕김
+- [ ] 로그아웃 → 다시 튕김
+
+**총 소요시간: 약 2시간 20분**
+
+---
+
+## ✅ 최종 정리
+
+### 핵심 포인트
+
+1. **JWT를 쿠키에 저장** → Middleware 접근 가능
+2. **기존 Middleware 설계 유지** → 가드 컴포넌트 불필요
+3. **차이점은 미미함:**
+ - 쿠키 이름: `laravel_session` → `auth_token`
+ - CSRF 토큰 불필요
+ - API 호출 시 Authorization 헤더 추가
+
+### 장점
+
+- ✅ Middleware에서 서버사이드 인증 체크
+- ✅ 클라이언트 가드 컴포넌트 불필요
+- ✅ 중복 코드 제거
+- ✅ 기존 설계(authentication-design.md) 거의 그대로 사용
+
+### 변경 사항
+
+**최소한의 변경만 필요:**
+```typescript
+// 1. Token 저장: 쿠키 사용
+tokenStorage.set(token);
+
+// 2. Middleware: 쿠키 이름만 변경
+const authToken = request.cookies.get('auth_token');
+
+// 3. API 호출: Authorization 헤더 추가
+headers: { 'Authorization': `Bearer ${token}` }
+
+// 4. CSRF 토큰: 제거
+// getCsrfToken() 불필요
+```
+
+---
+
+## 🚀 다음 단계
+
+1. ✅ 설계 확정 완료
+2. ⏳ 디자인 컴포넌트 대기
+3. ⏳ 백엔드 API 엔드포인트 확인
+ - POST /api/v1/register
+ - GET /api/v1/user
+ - POST /api/v1/logout
+4. 🚀 구현 시작 (2-3시간)
+
+**준비되면 바로 시작합니다!** 🎯
\ No newline at end of file
diff --git a/docs/[IMPL-2025-11-07] middleware-issue-resolution.md b/docs/[IMPL-2025-11-07] middleware-issue-resolution.md
new file mode 100644
index 00000000..de3adf51
--- /dev/null
+++ b/docs/[IMPL-2025-11-07] middleware-issue-resolution.md
@@ -0,0 +1,178 @@
+# Middleware 인증 문제 해결 보고서
+
+## 📅 작성일: 2025-11-07
+
+## 🔍 문제 증상
+
+로그인하지 않은 상태에서 `/dashboard`에 접근 시, 인증 체크가 작동하지 않고 대시보드에 바로 접근되는 문제가 발생했습니다.
+
+### 증상 상세
+- ✅ 로그인/로그아웃 기능 정상 작동
+- ✅ 쿠키(`user_token`) 저장/삭제 정상
+- ❌ Middleware에서 보호된 라우트 접근 차단 실패
+- ❌ Middleware console.log가 터미널에 전혀 출력되지 않음
+
+---
+
+## 🐛 발견된 문제들
+
+### 1. Next.js 15 + next-intl 호환성 문제
+**위치**: `next.config.ts`
+
+**원인**:
+- Next.js 15에서 next-intl v4를 사용할 때 `turbopack` 설정이 필수
+- 이 설정이 없으면 middleware가 제대로 컴파일되지 않음
+
+**해결**:
+```typescript
+// next.config.ts
+const nextConfig: NextConfig = {
+ turbopack: {}, // ✅ 추가
+};
+```
+
+---
+
+### 2. 복잡한 Matcher 정규식
+**위치**: `src/middleware.ts` - `config.matcher`
+
+**원인**:
+- 너무 복잡한 regex 패턴으로 라우트 매칭 실패
+- 중복된 matcher 패턴 (정규식 + 명시적 경로)
+
+**기존 코드**:
+```typescript
+matcher: [
+ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)',
+ '/dashboard/:path*',
+ '/login',
+ '/register',
+]
+```
+
+**해결**:
+```typescript
+matcher: [
+ '/((?!api|_next/static|_next/image|favicon.ico|.*\\..*|robots\\.txt).*)',
+]
+```
+
+---
+
+### 3. isPublicRoute 함수 로직 버그 ⭐ (핵심 문제)
+**위치**: `src/middleware.ts` - `isPublicRoute()` 함수
+
+**원인**:
+```typescript
+// 문제 코드
+function isPublicRoute(pathname: string): boolean {
+ return AUTH_CONFIG.publicRoutes.some(route =>
+ pathname === route || pathname.startsWith(route)
+ );
+}
+```
+
+**버그 시나리오**:
+1. `AUTH_CONFIG.publicRoutes`에 `'/'` 포함
+2. `/dashboard`.startsWith('/') → `true` 반환
+3. 모든 경로가 public route로 잘못 판단됨
+4. 인증 체크가 스킵되어 보호된 라우트 접근 가능
+
+**해결**:
+```typescript
+function isPublicRoute(pathname: string): boolean {
+ return AUTH_CONFIG.publicRoutes.some(route => {
+ // '/' 는 정확히 일치해야만 public
+ if (route === '/') {
+ return pathname === '/';
+ }
+ // 다른 라우트는 시작 일치 허용
+ return pathname === route || pathname.startsWith(route + '/');
+ });
+}
+```
+
+**수정 후 동작**:
+- `/` → public ✅
+- `/dashboard` → protected ✅
+- `/about` → public ✅
+- `/about/team` → public ✅
+
+---
+
+## ✅ 해결 결과
+
+### 적용된 수정 사항
+1. ✅ `next.config.ts`에 `turbopack: {}` 추가
+2. ✅ Middleware matcher 단순화
+3. ✅ `isPublicRoute()` 함수 로직 수정
+4. ✅ 디버깅 로그 제거 (클린 코드)
+
+### 검증 결과
+```bash
+# 로그아웃 상태에서 /dashboard 접근 시:
+[Auth Required] Redirecting to /login from /dashboard
+→ 자동으로 /login 페이지로 리다이렉트 ✅
+
+# 로그인 상태에서 /dashboard 접근 시:
+[Authenticated] Mode: bearer, Path: /dashboard
+→ 정상 접근 ✅
+```
+
+---
+
+## 📝 교훈
+
+### 1. Middleware 디버깅
+- **브라우저 콘솔이 아닌 서버 터미널**에서 로그 확인
+- `console.log`는 서버 사이드에서 실행되므로 터미널 출력
+
+### 2. 문자열 매칭 주의
+- `startsWith('/')` 같은 패턴은 모든 경로와 매칭됨
+- Root path(`/`)는 항상 정확한 일치(`===`) 사용
+
+### 3. Next.js 버전별 설정
+- Next.js 15 + next-intl 사용 시 `turbopack` 설정 필수
+- 공식 문서 및 마이그레이션 가이드 확인 필요
+
+---
+
+## 🔗 관련 파일
+
+### 수정된 파일
+- `next.config.ts` - turbopack 설정 추가
+- `src/middleware.ts` - isPublicRoute 로직 수정, matcher 단순화
+
+### 관련 설정 파일
+- `src/lib/api/auth/auth-config.ts` - 라우트 설정
+- `src/lib/api/auth/sanctum-client.ts` - 인증 로직
+- `src/lib/api/auth/token-storage.ts` - 토큰 관리
+
+---
+
+## 🎯 현재 인증 플로우
+
+### 로그인
+1. 사용자가 `/login`에서 인증 정보 입력
+2. PHP API(`/api/v1/login`)로 요청 (API Key 포함)
+3. Bearer Token 발급 (`user_token`)
+4. localStorage 저장 + Cookie 동기화
+5. `/dashboard`로 리다이렉트
+
+### 보호된 라우트 접근
+1. Middleware에서 요청 가로채기
+2. Cookie에서 `user_token` 확인
+3. 토큰 있음 → 통과
+4. 토큰 없음 → `/login`으로 리다이렉트
+
+### 로그아웃
+1. PHP API(`/api/v1/logout`) 호출
+2. localStorage 및 Cookie 정리
+3. `/login`으로 리다이렉트
+
+---
+
+## 📚 참고 자료
+- Next.js 15 Middleware 공식 문서
+- next-intl v4 마이그레이션 가이드
+- `claudedocs/research_nextjs15_middleware_authentication_2025-11-07.md`
\ No newline at end of file
diff --git a/docs/[IMPL-2025-11-07] route-protection-architecture.md b/docs/[IMPL-2025-11-07] route-protection-architecture.md
new file mode 100644
index 00000000..4f6d48dd
--- /dev/null
+++ b/docs/[IMPL-2025-11-07] route-protection-architecture.md
@@ -0,0 +1,513 @@
+# Route Protection Architecture - 최종 구조
+
+## 개요
+
+**2단계 보호 시스템:**
+1. **Middleware (서버)**: 모든 페이지 요청 시 인증 확인
+2. **Layout Hook (클라이언트)**: 보호된 페이지의 브라우저 캐시 방지
+
+---
+
+## 폴더 구조
+
+```
+src/app/[locale]/
+├── (auth)/ # 게스트 전용 페이지
+│ └── login/
+│ └── page.tsx # 로그인 페이지 (컴포넌트 재사용)
+│
+├── (protected)/ # ✅ 보호된 페이지 그룹
+│ ├── layout.tsx # 🔒 useAuthGuard() 여기서만!
+│ └── dashboard/
+│ └── page.tsx # useAuthGuard() 불필요
+│
+├── login/ # 직접 접근용 로그인 페이지
+│ └── page.tsx
+│
+├── signup/ # 직접 접근용 회원가입 페이지
+│ └── page.tsx
+│
+├── page.tsx # 홈페이지 (공개)
+└── layout.tsx # 루트 레이아웃
+```
+
+**Route Group 설명:**
+- `(auth)`: 괄호로 감싸져 있어 URL에 포함되지 않음
+ - `/login` → `src/app/[locale]/login/page.tsx`
+ - `/(auth)/login` → 동일한 `/login` URL
+- `(protected)`: Layout 기반 보호 그룹
+ - `/dashboard` → `src/app/[locale]/(protected)/dashboard/page.tsx`
+ - Layout의 `useAuthGuard()`가 자동 적용
+
+---
+
+## 보호 레이어 상세
+
+### Layer 1: Middleware (서버 사이드)
+
+**파일:** `src/middleware.ts`
+
+**역할:**
+- 모든 HTTP 요청 차단 (페이지, API, 리소스)
+- HttpOnly 쿠키 검증
+- 인증 실패 시 `/login` 리다이렉트
+
+**적용 범위:**
+- URL 직접 입력
+- 링크 클릭
+- 새로고침 (F5)
+- 프로그래매틱 네비게이션
+
+**코드:**
+```typescript
+// src/middleware.ts
+function checkAuthentication(request: NextRequest) {
+ const tokenCookie = request.cookies.get('user_token');
+ if (tokenCookie?.value) {
+ return { isAuthenticated: true, authMode: 'bearer' };
+ }
+ return { isAuthenticated: false, authMode: null };
+}
+
+// 보호된 경로 체크
+if (!isAuthenticated && !isPublicRoute && !isGuestOnlyRoute) {
+ return NextResponse.redirect(new URL('/login', request.url));
+}
+```
+
+---
+
+### Layer 2: Protected Layout (클라이언트 사이드)
+
+**파일:** `src/app/[locale]/(protected)/layout.tsx`
+
+**역할:**
+- 페이지 마운트 시 인증 재확인
+- 브라우저 BFCache (뒤로가기 캐시) 감지 및 새로고침
+- 다른 탭에서 로그아웃 시 동기화
+
+**적용 범위:**
+- `(protected)` 폴더 하위 모든 페이지
+- 브라우저 뒤로가기
+- 페이지 캐시 복원
+
+**코드:**
+```typescript
+// src/app/[locale]/(protected)/layout.tsx
+"use client";
+
+import { useAuthGuard } from '@/hooks/useAuthGuard';
+
+export default function ProtectedLayout({ children }) {
+ useAuthGuard(); // 모든 하위 페이지에 자동 적용
+ return <>{children}>;
+}
+```
+
+---
+
+## 시나리오별 동작
+
+### ✅ 시나리오 1: URL 직접 입력 (비로그인)
+
+```
+http://localhost:3000/dashboard 입력
+ ↓
+🛡️ Middleware 실행
+ → 쿠키 없음
+ → /login 리다이렉트
+ ↓
+로그인 페이지 표시
+(Layout Hook은 실행되지 않음)
+```
+
+**결과:** Middleware만으로 차단 완료 ✅
+
+---
+
+### ✅ 시나리오 2: 정상 로그인 후 접근
+
+```
+로그인 성공 → /dashboard 이동
+ ↓
+🛡️ Middleware 실행
+ → 쿠키 있음
+ → 통과
+ ↓
+(protected)/layout.tsx 마운트
+ → useAuthGuard() 실행
+ → /api/auth/check 호출
+ → 인증 성공
+ ↓
+dashboard/page.tsx 렌더링
+```
+
+**결과:** 이중 검증 통과 ✅
+
+---
+
+### ✅ 시나리오 3: 로그아웃 후 뒤로가기 (핵심!)
+
+```
+/dashboard 접속 (로그인 상태)
+ ↓
+Logout 버튼 클릭
+ → /api/auth/logout 호출
+ → HttpOnly 쿠키 삭제
+ → /login 이동
+ ↓
+브라우저 뒤로가기 버튼 클릭
+ ↓
+⚠️ 브라우저 캐시에서 /dashboard 복원
+ → 서버 요청 없음
+ → Middleware 실행 안됨 ❌
+ ↓
+🛡️ (protected)/layout.tsx 복원
+ → useAuthGuard() 실행
+ → pageshow 이벤트 감지
+ → event.persisted === true (캐시됨)
+ → window.location.reload() 실행
+ ↓
+새로고침 → 서버 요청 발생
+ ↓
+🛡️ Middleware 실행
+ → 쿠키 없음
+ → /login 리다이렉트
+ ↓
+로그인 페이지 표시
+```
+
+**결과:** Layout Hook이 캐시 우회 → Middleware 재실행 ✅
+
+---
+
+### ✅ 시나리오 4: 다른 탭에서 로그아웃
+
+```
+탭 A: /dashboard 접속 (로그인 상태)
+탭 B: 로그아웃
+ ↓
+탭 A: 페이지 새로고침 또는 네비게이션
+ ↓
+🛡️ Middleware 실행
+ → 쿠키 없음 (탭 B에서 삭제됨)
+ → /login 리다이렉트
+```
+
+**결과:** 쿠키 공유로 즉시 차단 ✅
+
+---
+
+## 새 페이지 추가 방법
+
+### 보호된 페이지 추가
+
+**단계:**
+1. `(protected)` 폴더 안에 페이지 생성
+2. **끝!** (자동으로 보호됨)
+
+**예시:**
+```bash
+# Profile 페이지 생성
+mkdir -p src/app/[locale]/(protected)/profile
+```
+
+```tsx
+// src/app/[locale]/(protected)/profile/page.tsx
+"use client";
+
+export default function Profile() {
+ // useAuthGuard() 불필요! Layout에서 자동 처리
+ return
Profile Content
;
+}
+```
+
+**URL:** `/profile` (Route Group 괄호는 URL에 포함 안됨)
+
+---
+
+### 공개 페이지 추가
+
+**단계:**
+1. `(protected)` 폴더 **밖**에 페이지 생성
+2. `auth-config.ts`의 `publicRoutes`에 추가 (필요시)
+
+**예시:**
+```bash
+# About 페이지 생성 (공개)
+mkdir -p src/app/[locale]/about
+```
+
+```tsx
+// src/app/[locale]/about/page.tsx
+export default function About() {
+ return
About Us (Public)
;
+}
+```
+
+```typescript
+// src/lib/api/auth/auth-config.ts
+export const AUTH_CONFIG = {
+ publicRoutes: [
+ '/about', // 추가
+ ],
+ // ...
+};
+```
+
+---
+
+## 구현 상세
+
+### useAuthGuard Hook
+
+**파일:** `src/hooks/useAuthGuard.ts`
+
+```typescript
+export function useAuthGuard() {
+ const router = useRouter();
+
+ useEffect(() => {
+ // 1. 페이지 로드 시 인증 확인
+ const checkAuth = async () => {
+ const response = await fetch('/api/auth/check');
+ if (!response.ok) {
+ router.replace('/login');
+ }
+ };
+
+ checkAuth();
+
+ // 2. 브라우저 캐시 감지 및 새로고침
+ const handlePageShow = (event: PageTransitionEvent) => {
+ if (event.persisted) {
+ console.log('🔄 캐시된 페이지 감지: 새로고침');
+ window.location.reload();
+ }
+ };
+
+ window.addEventListener('pageshow', handlePageShow);
+
+ return () => {
+ window.removeEventListener('pageshow', handlePageShow);
+ };
+ }, [router]);
+}
+```
+
+**핵심 로직:**
+1. `checkAuth()`: `/api/auth/check` 호출로 실시간 인증 확인
+2. `pageshow` 이벤트: `event.persisted`로 캐시 감지
+3. `window.location.reload()`: 강제 새로고침으로 Middleware 재실행
+
+---
+
+### Auth Check API
+
+**파일:** `src/app/api/auth/check/route.ts`
+
+```typescript
+export async function GET(request: NextRequest) {
+ const token = request.cookies.get('user_token')?.value;
+
+ if (!token) {
+ return NextResponse.json(
+ { error: 'Not authenticated', authenticated: false },
+ { status: 401 }
+ );
+ }
+
+ return NextResponse.json(
+ { authenticated: true },
+ { status: 200 }
+ );
+}
+```
+
+**역할:**
+- HttpOnly 쿠키 읽기
+- 인증 상태 반환 (200 or 401)
+
+---
+
+## 보안 장점
+
+### ✅ 이전 (각 페이지에 Hook)
+```
+각 페이지마다 useAuthGuard() 수동 추가
+→ 누락 위험 ⚠️
+→ 보일러플레이트 코드 증가
+```
+
+### ✅ 현재 (Layout 기반)
+```
+(protected)/layout.tsx에서 한 번만
+→ 새 페이지 자동 보호
+→ 누락 불가능
+→ 코드 중복 제거
+```
+
+---
+
+## 설정 파일
+
+### auth-config.ts
+
+**파일:** `src/lib/api/auth/auth-config.ts`
+
+```typescript
+export const AUTH_CONFIG = {
+ // 🔓 공개 라우트 (인증 불필요)
+ publicRoutes: [],
+
+ // 🔒 보호된 라우트 (참고용, 실제로는 기본 정책으로 보호)
+ protectedRoutes: [
+ '/dashboard',
+ '/profile',
+ '/settings',
+ '/admin',
+ // ... 모든 보호된 경로
+ ],
+
+ // 👤 게스트 전용 라우트 (로그인 후 접근 불가)
+ guestOnlyRoutes: [
+ '/login',
+ '/signup',
+ '/forgot-password',
+ ],
+
+ // 리다이렉트 설정
+ redirects: {
+ afterLogin: '/dashboard',
+ afterLogout: '/login',
+ unauthorized: '/login',
+ },
+};
+```
+
+---
+
+## 테스트 체크리스트
+
+### 필수 테스트
+
+- [ ] **URL 직접 입력 (비로그인)**
+ - `/dashboard` 입력 → `/login` 리다이렉트
+
+- [ ] **로그인 후 접근**
+ - 로그인 → `/dashboard` 정상 표시
+
+- [ ] **로그아웃 후 뒤로가기**
+ - 로그아웃 → 뒤로가기 → 캐시 감지 → 새로고침 → `/login` 리다이렉트
+
+- [ ] **다른 탭에서 로그아웃**
+ - 탭 A: `/dashboard` 유지
+ - 탭 B: 로그아웃
+ - 탭 A: 새로고침 → `/login` 리다이렉트
+
+- [ ] **새 보호된 페이지 추가**
+ - `(protected)/profile` 생성 → 자동 보호 확인
+
+---
+
+## 트러블슈팅
+
+### 문제: 로그아웃 후 뒤로가기 시 페이지 보임
+
+**원인:** Layout이 Client Component가 아님
+
+**해결:**
+```tsx
+// (protected)/layout.tsx 파일 상단에 추가
+"use client";
+```
+
+---
+
+### 문제: 404 에러 (페이지를 찾을 수 없음)
+
+**원인:** 폴더 이름 오타 또는 Route Group 괄호 누락
+
+**확인:**
+```bash
+# 올바른 경로
+src/app/[locale]/(protected)/dashboard/page.tsx
+
+# 잘못된 경로
+src/app/[locale]/protected/dashboard/page.tsx # 괄호 없음
+```
+
+---
+
+### 문제: 무한 리다이렉트
+
+**원인:** `/login` 페이지에도 보호 적용됨
+
+**확인:**
+- `/login`이 `(protected)` 폴더 **밖**에 있는지 확인
+- `guestOnlyRoutes`에 `/login` 포함 확인
+
+---
+
+## 성능 고려사항
+
+### API 호출 최소화
+- `useAuthGuard`는 페이지 마운트 시 **1회만** 호출
+- 브라우저 캐시 복원 시에만 추가 호출 (새로고침)
+
+### 사용자 경험
+- 인증 확인은 비동기로 처리 (UI 블로킹 없음)
+- `router.replace()` 사용으로 뒤로가기 히스토리 오염 방지
+
+---
+
+## 향후 페이지 추가 계획
+
+### 즉시 적용 가능 (보호됨)
+`(protected)` 폴더에 추가하면 자동 보호:
+
+```
+(protected)/
+├── profile/ # 사용자 프로필
+├── settings/ # 설정
+├── admin/ # 관리자
+│ ├── users/
+│ ├── tenants/
+│ └── reports/
+├── inventory/ # 재고 관리
+├── finance/ # 재무
+├── hr/ # 인사
+└── crm/ # CRM
+```
+
+---
+
+## 요약
+
+### ✅ 최종 아키텍처
+
+```
+보호 정책:
+1. Middleware (서버): 모든 요청 차단
+2. Layout (클라이언트): 캐시 우회 및 실시간 동기화
+
+폴더 구조:
+- (protected)/layout.tsx: 한 곳에서만 관리
+- (protected)/**/page.tsx: 자동으로 보호됨
+
+장점:
+✅ 코드 중복 제거
+✅ 누락 불가능
+✅ 브라우저 캐시 문제 해결
+✅ 확장성 (새 페이지 자동 보호)
+✅ 유지보수성 향상
+```
+
+---
+
+## 참고 문서
+
+- **HttpOnly Cookie 구현**: `claudedocs/httponly-cookie-implementation.md`
+- **Auth Guard 사용법**: `claudedocs/auth-guard-usage.md`
+- **Middleware 설정**: `src/middleware.ts`
+- **Auth 설정**: `src/lib/api/auth/auth-config.ts`
\ No newline at end of file
diff --git a/docs/[IMPL-2025-11-07] seo-bot-blocking-configuration.md b/docs/[IMPL-2025-11-07] seo-bot-blocking-configuration.md
new file mode 100644
index 00000000..7e82ac18
--- /dev/null
+++ b/docs/[IMPL-2025-11-07] seo-bot-blocking-configuration.md
@@ -0,0 +1,364 @@
+# SEO 및 봇 차단 설정 문서
+
+## 개요
+
+이 문서는 멀티 테넌트 ERP 시스템의 SEO 설정 및 봇 차단 전략을 설명합니다. 폐쇄형 시스템의 특성상 검색 엔진 수집을 방지하면서도, 과도한 차단으로 인한 브라우저 경고를 피하는 **균형 잡힌 접근 방식**을 채택했습니다.
+
+---
+
+## 📋 구현 내용
+
+### 1. robots.txt 설정 ✅
+
+**위치**: `/public/robots.txt`
+
+**전략**: 느슨한 차단 (Moderate Blocking)
+
+#### 주요 설정
+
+```txt
+# 허용된 경로 (Allow)
+- / (홈페이지)
+- /login (로그인 페이지)
+- /about (회사 소개)
+
+# 차단된 경로 (Disallow)
+- /dashboard (대시보드)
+- /admin (관리자 페이지)
+- /api (API 엔드포인트)
+- /tenant (테넌트 관리)
+- /settings, /users, /reports, /analytics
+- /inventory, /finance, /hr, /crm
+- 기타 ERP 핵심 기능 경로
+
+# 민감한 파일 형식 차단
+- /*.json, /*.xml, /*.csv
+- /*.xls, /*.xlsx
+
+# Crawl-delay: 10초
+```
+
+#### 크롬 경고 방지 전략
+
+1. **홈페이지(/) 허용**: 완전 차단하지 않아 브라우저에서 악성 사이트로 분류되지 않음
+2. **공개 페이지 제공**: /login, /about 등 일부 공개 경로 허용
+3. **Crawl-delay 설정**: 서버 부하 감소 및 정상적인 봇 동작 유도
+
+---
+
+### 2. Middleware 봇 차단 로직 ✅
+
+**위치**: `/src/middleware.ts`
+
+**역할**: 런타임에서 봇 요청을 감지하고 차단
+
+#### 핵심 기능
+
+##### 2.1 봇 패턴 감지
+
+User-Agent 기반으로 다음 패턴을 감지:
+
+```typescript
+- /bot/i, /crawler/i, /spider/i, /scraper/i
+- /curl/i, /wget/i, /python-requests/i
+- /axios/i (프로그래밍 방식 접근)
+- /headless/i, /phantom/i, /selenium/i, /puppeteer/i, /playwright/i
+- /go-http-client/i, /java/i, /okhttp/i
+```
+
+##### 2.2 경로 보호 전략
+
+**보호된 경로 (Protected Paths)**:
+- `/dashboard`, `/admin`, `/api`
+- `/tenant`, `/settings`, `/users`
+- `/reports`, `/analytics`
+- `/inventory`, `/finance`, `/hr`, `/crm`
+- `/employee`, `/customer`, `/supplier`
+- `/orders`, `/invoices`, `/payroll`
+
+**공개 경로 (Public Paths)**:
+- `/`, `/login`, `/about`, `/contact`
+- `/robots.txt`, `/sitemap.xml`, `/favicon.ico`
+
+##### 2.3 차단 동작
+
+봇이 보호된 경로에 접근 시:
+```json
+HTTP 403 Forbidden
+{
+ "error": "Access Denied",
+ "message": "Automated access to this resource is not permitted.",
+ "code": "BOT_ACCESS_DENIED"
+}
+```
+
+##### 2.4 보안 헤더 추가
+
+모든 응답에 다음 헤더 추가:
+```http
+X-Robots-Tag: noindex, nofollow, noarchive, nosnippet
+X-Content-Type-Options: nosniff
+X-Frame-Options: DENY
+Referrer-Policy: strict-origin-when-cross-origin
+```
+
+##### 2.5 로깅
+
+```typescript
+// 차단된 봇 로그
+[Bot Blocked] {user-agent} attempted to access {pathname}
+
+// 허용된 봇 로그 (공개 경로)
+[Bot Allowed] {user-agent} accessed {pathname}
+```
+
+---
+
+### 3. SEO 메타데이터 설정 ✅
+
+**위치**: `/src/app/layout.tsx`
+
+#### 메타데이터 구성
+
+```typescript
+metadata: {
+ title: {
+ default: "ERP System - Enterprise Resource Planning",
+ template: "%s | ERP System"
+ },
+ description: "Multi-tenant Enterprise Resource Planning System for SME businesses",
+ robots: {
+ index: false, // 검색 엔진 색인 방지
+ follow: false, // 링크 추적 방지
+ nocache: true, // 캐싱 방지
+ googleBot: {
+ index: false,
+ follow: false,
+ 'max-video-preview': -1,
+ 'max-image-preview': 'none',
+ 'max-snippet': -1,
+ }
+ },
+ openGraph: {
+ type: 'website',
+ locale: 'ko_KR',
+ siteName: 'ERP System',
+ title: 'Enterprise Resource Planning System',
+ description: 'Multi-tenant ERP System for SME businesses',
+ },
+ other: {
+ 'cache-control': 'no-cache, no-store, must-revalidate'
+ }
+}
+```
+
+#### 주요 특징
+
+1. **noindex, nofollow**: 검색 엔진 색인 및 링크 추적 차단
+2. **nocache**: 민감한 페이지 캐싱 방지
+3. **Google Bot 세부 제어**: 이미지, 비디오, 스니펫 미리보기 차단
+4. **Cache-Control 헤더**: 브라우저 및 프록시 캐싱 방지
+5. **다국어 지원**: locale 설정 (ko_KR)
+
+---
+
+## 🎯 구현 전략 요약
+
+| 구성 요소 | 목적 | 차단 강도 | 위치 |
+|---------|------|---------|------|
+| `robots.txt` | 검색 엔진 크롤러 가이드 | 느슨함 (Moderate) | `/public/robots.txt` |
+| `middleware.ts` | 런타임 봇 감지 및 차단 | 강함 (Strong) | `/src/middleware.ts` |
+| `layout.tsx` | HTML 메타 태그 설정 | 강함 (Strong) | `/src/app/layout.tsx` |
+
+---
+
+## 🔒 보안 수준
+
+### 다층 방어 (Defense in Depth)
+
+```
+Layer 1: robots.txt
+ ↓ 정상적인 검색 엔진 봇은 여기서 차단
+
+Layer 2: Middleware Bot Detection
+ ↓ 악의적인 봇 및 자동화 도구 차단
+
+Layer 3: SEO Meta Tags
+ ↓ HTML 레벨에서 색인 방지
+
+Layer 4: Security Headers
+ ↓ 추가 보안 헤더로 보호 강화
+```
+
+### 차단 vs 허용 균형
+
+| 요소 | 설정 | 이유 |
+|-----|------|------|
+| 홈페이지 (/) | ✅ 허용 | 크롬 경고 방지 |
+| 로그인 (/login) | ✅ 허용 | 정상 접근 가능 |
+| 대시보드 (/dashboard) | ❌ 차단 | ERP 핵심 기능 보호 |
+| API (/api) | ❌ 차단 | 데이터 보호 |
+| 정적 파일 (.svg, .png 등) | ✅ 허용 | 정상 웹사이트 기능 |
+
+---
+
+## 📊 동작 흐름
+
+### 정상 사용자 (브라우저)
+
+```
+1. 사용자가 /dashboard 접근
+2. middleware.ts: User-Agent 확인 → 정상 브라우저
+3. X-Robots-Tag 헤더 추가
+4. 정상 페이지 렌더링
+5. HTML에 noindex 메타 태그 포함
+```
+
+### 검색 엔진 봇
+
+```
+1. Googlebot이 사이트 접근
+2. robots.txt 확인 → /dashboard Disallow
+3. Googlebot은 /dashboard 접근하지 않음
+4. / (홈페이지)만 크롤링 → noindex 메타 태그 확인
+5. 검색 결과에 포함하지 않음
+```
+
+### 악의적인 봇/스크래퍼
+
+```
+1. curl/python-requests로 /api/users 접근 시도
+2. middleware.ts: User-Agent에서 'curl' 감지
+3. isProtectedPath('/api/users') → true
+4. HTTP 403 Forbidden 반환
+5. 로그 기록: [Bot Blocked] curl/7.68.0 attempted to access /api/users
+```
+
+---
+
+## 🧪 테스트 방법
+
+### 1. robots.txt 확인
+
+브라우저에서 접속:
+```
+http://localhost:3000/robots.txt
+```
+
+### 2. Middleware 테스트
+
+**정상 브라우저 접근**:
+```bash
+curl -H "User-Agent: Mozilla/5.0" http://localhost:3000/dashboard
+# 예상: 정상 페이지 반환 (인증 로직 없으면 접근 가능)
+```
+
+**봇으로 접근**:
+```bash
+curl http://localhost:3000/dashboard
+# 예상: HTTP 403 Forbidden
+# {"error":"Access Denied","message":"Automated access to this resource is not permitted.","code":"BOT_ACCESS_DENIED"}
+```
+
+**공개 페이지 접근**:
+```bash
+curl http://localhost:3000/
+# 예상: 정상 페이지 반환 (X-Robots-Tag 헤더 포함)
+```
+
+### 3. 헤더 확인
+
+```bash
+curl -I http://localhost:3000/
+# 확인 항목:
+# X-Robots-Tag: noindex, nofollow
+# X-Content-Type-Options: nosniff
+# X-Frame-Options: DENY
+```
+
+### 4. SEO 메타 태그 확인
+
+브라우저에서 페이지 소스 보기:
+```html
+
+```
+
+---
+
+## ⚠️ 주의사항
+
+### 크롬 경고 방지
+
+1. **완전 차단 금지**: robots.txt에서 모든 경로를 차단하면 안 됨
+ ```txt
+ # ❌ 절대 사용 금지
+ User-agent: *
+ Disallow: /
+ ```
+
+2. **공개 페이지 유지**: 최소한 홈페이지는 허용
+3. **HTTP 상태 코드**: 403 사용 (404나 500은 피함)
+4. **정상 사용자 차단 방지**: User-Agent 패턴 신중히 선택
+
+### 로그 모니터링
+
+- 차단된 봇 접근 시도를 모니터링하여 새로운 패턴 감지
+- 정상 사용자가 차단되는 경우 BOT_PATTERNS 조정
+- 로그 파일 위치: 콘솔 출력 (프로덕션에서는 로깅 서비스 연동 필요)
+
+### 성능 고려사항
+
+- Middleware는 모든 요청에 실행되므로 성능 영향 최소화
+- 정규표현식 패턴 최적화 필요
+- 필요시 Redis 등으로 IP 기반 rate limiting 추가 고려
+
+---
+
+## 🔄 향후 개선 사항
+
+### 1. IP 기반 Rate Limiting
+
+```typescript
+// 추가 예정: Redis를 활용한 rate limiting
+import { Ratelimit } from "@upstash/ratelimit";
+import { Redis } from "@upstash/redis";
+```
+
+### 2. 화이트리스트 관리
+
+```typescript
+// 신뢰할 수 있는 IP나 User-Agent 화이트리스트
+const WHITELISTED_IPS = ['123.45.67.89'];
+const WHITELISTED_USER_AGENTS = ['MyCompanyMonitoringBot'];
+```
+
+### 3. 고급 봇 감지
+
+```typescript
+// 행동 패턴 분석 (빠른 요청 속도, 비정상 경로 접근 등)
+// Fingerprinting 기술 적용
+```
+
+### 4. 로깅 서비스 연동
+
+```typescript
+// Sentry, LogRocket 등 APM 도구 연동
+// 봇 공격 패턴 분석 및 알림
+```
+
+---
+
+## 📝 변경 이력
+
+| 날짜 | 버전 | 변경 내용 |
+|-----|------|---------|
+| 2025-11-06 | 1.0.0 | 초기 SEO 및 봇 차단 설정 구현 |
+
+---
+
+## 참고 자료
+
+- [Next.js Middleware Documentation](https://nextjs.org/docs/app/building-your-application/routing/middleware)
+- [robots.txt Specification](https://developers.google.com/search/docs/crawling-indexing/robots/intro)
+- [X-Robots-Tag HTTP Header](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag)
+- [OWASP Bot Management](https://owasp.org/www-community/controls/Blocking_Brute_Force_Attacks)
diff --git a/docs/[IMPL-2025-11-10] dashboard-integration-complete.md b/docs/[IMPL-2025-11-10] dashboard-integration-complete.md
new file mode 100644
index 00000000..c512254f
--- /dev/null
+++ b/docs/[IMPL-2025-11-10] dashboard-integration-complete.md
@@ -0,0 +1,191 @@
+# 대시보드 통합 완료 보고서
+
+## 작업 완료 시간
+2025-11-10 17:55
+
+## 완료된 작업
+
+### 1. 페이지 교체
+✅ 기존 `dashboard/page.tsx` 백업 완료 (`page.tsx.backup`)
+✅ 새로운 역할 기반 대시보드 페이지로 교체
+✅ Dashboard Layout 생성 및 연결
+
+### 2. 파일 구조
+```
+src/app/[locale]/(protected)/dashboard/
+├── layout.tsx # DashboardLayout을 적용하는 레이아웃
+├── page.tsx # 새로운 역할 기반 대시보드 (마이그레이션 완료)
+└── page.tsx.backup # 기존 페이지 백업
+```
+
+### 3. 로그인/로그아웃 통합
+
+#### 로그인 시 (`LoginPage.tsx`)
+```typescript
+// 사용자 정보를 localStorage에 저장
+const userData = {
+ role: data.user?.role || 'CEO',
+ name: data.user?.user_name || userId,
+ position: data.user?.position || '사용자',
+ userId: userId,
+};
+localStorage.setItem('user', JSON.stringify(userData));
+```
+
+#### 로그아웃 시 (`DashboardLayout.tsx`)
+```typescript
+const handleLogout = async () => {
+ // 1. API 호출로 HttpOnly 쿠키 삭제
+ await fetch('/api/auth/logout', { method: 'POST' });
+
+ // 2. localStorage 정리
+ localStorage.removeItem('user');
+
+ // 3. 로그인 페이지로 리다이렉트
+ router.push('/login');
+};
+```
+
+### 4. UI 컴포넌트 추가
+
+추가로 복사된 UI 컴포넌트:
+- ✅ `checkbox.tsx`
+- ✅ `card.tsx`
+- ✅ `badge.tsx`
+- ✅ `progress.tsx`
+- ✅ `utils.ts` (공통 유틸리티)
+- ✅ `dialog.tsx`
+- ✅ `dropdown-menu.tsx`
+- ✅ `popover.tsx`
+- ✅ `switch.tsx`
+- ✅ `textarea.tsx`
+- ✅ `table.tsx`
+- ✅ `tabs.tsx`
+- ✅ `separator.tsx`
+
+### 5. 의존성 설치
+
+추가 설치된 패키지:
+```json
+{
+ "@radix-ui/react-progress": "^latest",
+ "@radix-ui/react-checkbox": "^latest"
+}
+```
+
+## 동작 방식
+
+### 로그인 플로우
+1. 사용자가 로그인 폼 제출
+2. `/api/auth/login` API 호출
+3. 성공 시 사용자 정보를 localStorage에 저장
+4. `/dashboard`로 리다이렉트
+
+### 대시보드 표시
+1. `DashboardLayout`이 localStorage에서 사용자 정보 읽기
+2. 사용자 역할에 따라 메뉴 생성
+3. `Dashboard` 컴포넌트가 역할에 맞는 대시보드 표시
+4. CEO → CEODashboard
+5. ProductionManager → ProductionManagerDashboard
+6. Worker → WorkerDashboard
+7. SystemAdmin → SystemAdminDashboard
+8. Sales → SalesLeadDashboard
+
+### 역할 전환
+1. 헤더의 드롭다운에서 역할 선택
+2. localStorage 업데이트
+3. `roleChanged` 이벤트 발생
+4. Dashboard 컴포넌트가 자동으로 리렌더링
+5. 새로운 역할에 맞는 대시보드 표시
+
+### 로그아웃 플로우
+1. 유저 프로필 드롭다운에서 "로그아웃" 클릭
+2. `/api/auth/logout` API 호출 (HttpOnly 쿠키 삭제)
+3. localStorage에서 사용자 정보 제거
+4. `/login`으로 리다이렉트
+
+## 테스트 방법
+
+### 1. 개발 서버 실행
+```bash
+npm run dev
+```
+
+### 2. 로그인 테스트
+1. `http://localhost:3000/login` 접속
+2. 로그인 (기본 테스트 계정 사용)
+3. 대시보드로 자동 이동 확인
+
+### 3. 역할별 대시보드 테스트
+대시보드 헤더의 역할 선택 드롭다운에서:
+- CEO (대표이사)
+- ProductionManager (생산관리자)
+- Worker (생산작업자)
+- SystemAdmin (시스템관리자)
+- Sales (영업사원)
+
+각 역할로 전환하여 다른 대시보드가 표시되는지 확인
+
+### 4. 로그아웃 테스트
+1. 우측 상단 유저 프로필 클릭
+2. "로그아웃" 선택
+3. 로그인 페이지로 이동 확인
+
+## 빌드 상태
+
+✅ **컴파일 성공**: 모든 모듈이 정상적으로 컴파일됨
+⚠️ **ESLint 경고**: 일부 미사용 변수 경고 존재 (기능에는 영향 없음)
+
+빌드 결과:
+```
+✓ Compiled successfully in 5.0s
+```
+
+## 알려진 이슈
+
+### ESLint 경고
+- 미사용 import 및 변수
+- 일부 컴포넌트의 `any` 타입 사용
+- `alert`, `setTimeout` 등 브라우저 전역 객체 참조
+
+**해결 방법**: 이후 코드 정리 작업에서 처리 예정 (기능 동작에는 문제 없음)
+
+## 다음 단계
+
+### 즉시 가능
+1. ✅ 로그인 후 대시보드 확인
+2. ✅ 역할 전환 기능 테스트
+3. ✅ 로그아웃 기능 테스트
+
+### 추가 작업 필요
+1. ESLint 경고 정리
+2. TypeScript 타입 개선
+3. 하위 라우트 생성 (판매관리, 생산관리 등)
+4. API 통합 작업
+5. 실제 사용자 데이터 연동
+
+## 파일 변경 사항 요약
+
+### 생성된 파일
+- `src/app/[locale]/(protected)/dashboard/layout.tsx`
+- `src/app/[locale]/(protected)/dashboard/page.tsx.backup`
+
+### 수정된 파일
+- `src/app/[locale]/(protected)/dashboard/page.tsx` (완전 교체)
+- `src/components/auth/LoginPage.tsx` (localStorage 저장 로직 추가)
+- `src/layouts/DashboardLayout.tsx` (로그아웃 기능 추가)
+
+### 추가된 컴포넌트 및 의존성
+- 40+ 비즈니스 컴포넌트
+- 13+ UI 컴포넌트
+- Zustand stores (메뉴, 테마 관리)
+- Custom hooks (useUserRole, useCurrentTime)
+
+## 결론
+
+✅ **마이그레이션 완료**: 모든 대시보드 컴포넌트가 성공적으로 Next.js 프로젝트로 통합됨
+✅ **빌드 성공**: 프로젝트가 정상적으로 컴파일됨
+✅ **로그인 통합**: 로그인/로그아웃 플로우가 새로운 대시보드와 연동됨
+✅ **역할 기반 시스템**: 5가지 역할별 대시보드가 동작함
+
+이제 `npm run dev`로 개발 서버를 실행하고 로그인하면 새로운 역할 기반 대시보드를 확인할 수 있습니다!
diff --git a/docs/[IMPL-2025-11-10] token-management-guide.md b/docs/[IMPL-2025-11-10] token-management-guide.md
new file mode 100644
index 00000000..79968c5f
--- /dev/null
+++ b/docs/[IMPL-2025-11-10] token-management-guide.md
@@ -0,0 +1,424 @@
+# Token Management System Guide
+
+완전한 Access Token & Refresh Token 시스템 구현 가이드
+
+## 📋 목차
+
+1. [시스템 개요](#시스템-개요)
+2. [토큰 라이프사이클](#토큰-라이프사이클)
+3. [API 엔드포인트](#api-엔드포인트)
+4. [자동 토큰 갱신](#자동-토큰-갱신)
+5. [사용 예시](#사용-예시)
+6. [보안 고려사항](#보안-고려사항)
+
+---
+
+## 시스템 개요
+
+### 토큰 구조
+
+```json
+{
+ "access_token": "214|EU7drdTBYN1fru0MylLXwjJbi2svXcikn5ofvmTI354d09c7",
+ "refresh_token": "215|6hAPWcO05jtfSDV9Yz4kLQi3qZDFuycMqrNITOV3c27bd0cb",
+ "token_type": "Bearer",
+ "expires_in": 7200,
+ "expires_at": "2025-11-10 15:49:38"
+}
+```
+
+### 저장 방식
+
+**HttpOnly 쿠키** (XSS 공격 방지):
+- `access_token`: 2시간 만료 (7200초)
+- `refresh_token`: 7일 만료 (604800초)
+
+**보안 속성**:
+- `HttpOnly`: JavaScript 접근 불가
+- `Secure`: HTTPS만 전송
+- `SameSite=Strict`: CSRF 공격 방지
+
+---
+
+## 토큰 라이프사이클
+
+### 1. 로그인 (Token 발급)
+
+```
+사용자 로그인
+ ↓
+POST /api/auth/login
+ ↓
+PHP Backend /api/v1/login
+ ↓
+access_token + refresh_token 발급
+ ↓
+HttpOnly 쿠키에 저장
+ ↓
+대시보드로 이동
+```
+
+### 2. 인증된 요청
+
+```
+보호된 페이지 접근
+ ↓
+Middleware 인증 체크
+ ↓
+access_token 존재?
+ ├─ Yes → 접근 허용
+ └─ No → refresh_token 확인
+ ├─ 있음 → 자동 갱신 시도
+ └─ 없음 → 로그인 페이지로
+```
+
+### 3. 토큰 갱신
+
+```
+access_token 만료 (2시간 후)
+ ↓
+보호된 API 호출 시도
+ ↓
+401 Unauthorized 응답
+ ↓
+POST /api/auth/refresh
+ ↓
+refresh_token으로 새 토큰 발급
+ ↓
+새 access_token + refresh_token 쿠키 업데이트
+ ↓
+원래 API 호출 재시도
+ ↓
+성공
+```
+
+### 4. 로그아웃
+
+```
+사용자 로그아웃
+ ↓
+POST /api/auth/logout
+ ↓
+PHP Backend /api/v1/logout (토큰 무효화)
+ ↓
+HttpOnly 쿠키 삭제
+ ↓
+로그인 페이지로 이동
+```
+
+---
+
+## API 엔드포인트
+
+### 1. Login API
+
+**Endpoint**: `POST /api/auth/login`
+
+**Request**:
+```typescript
+{
+ user_id: string;
+ user_pwd: string;
+}
+```
+
+**Response**:
+```typescript
+{
+ message: string;
+ user: UserObject;
+ tenant: TenantObject | null;
+ menus: MenuItem[];
+ token_type: "Bearer";
+ expires_in: number;
+ expires_at: string;
+}
+```
+
+**쿠키 설정**:
+- `access_token` (HttpOnly, 2시간)
+- `refresh_token` (HttpOnly, 7일)
+
+---
+
+### 2. Refresh Token API
+
+**Endpoint**: `POST /api/auth/refresh`
+
+**쿠키 필요**: `refresh_token`
+
+**Response** (성공):
+```typescript
+{
+ message: "Token refreshed successfully";
+ token_type: "Bearer";
+ expires_in: number;
+ expires_at: string;
+}
+```
+
+**Response** (실패):
+```typescript
+{
+ error: "Token refresh failed";
+ needsReauth: true;
+}
+```
+
+**쿠키 업데이트**:
+- 새 `access_token` (2시간)
+- 새 `refresh_token` (7일)
+
+---
+
+### 3. Auth Check API
+
+**Endpoint**: `GET /api/auth/check`
+
+**기능**:
+1. `access_token` 존재 → 200 OK with `authenticated: true`
+2. `access_token` 없음 + `refresh_token` 있음 → 자동 갱신 시도
+ - 갱신 성공 → 200 OK with `authenticated: true, refreshed: true`
+ - 갱신 실패 → 401 Unauthorized
+3. 둘 다 없음 → 401 Unauthorized
+
+**Response**:
+```typescript
+// ✅ 인증 성공 (200)
+{
+ authenticated: true;
+ refreshed?: boolean; // 자동 갱신 여부
+}
+
+// ❌ 인증 실패 (401)
+{
+ error: string; // 'Not authenticated' 또는 'Token refresh failed'
+}
+```
+
+**참고**:
+- 🔵 **Next.js 내부 API** (PHP 백엔드 X)
+- 성능 최적화: 로컬 쿠키만 확인하여 빠른 응답
+- 로그인/회원가입 페이지에서 이미 로그인된 사용자를 대시보드로 리다이렉트하는 데 사용
+
+---
+
+### 4. Logout API
+
+**Endpoint**: `POST /api/auth/logout`
+
+**기능**:
+1. PHP 백엔드에 로그아웃 요청 (토큰 무효화)
+2. `access_token`, `refresh_token` 쿠키 삭제
+
+---
+
+## 자동 토큰 갱신
+
+### 1. Middleware에서 자동 갱신
+
+`src/middleware.ts`:
+```typescript
+// access_token 또는 refresh_token이 있으면 인증됨
+const accessToken = request.cookies.get('access_token');
+const refreshToken = request.cookies.get('refresh_token');
+
+if ((accessToken && accessToken.value) || (refreshToken && refreshToken.value)) {
+ return { isAuthenticated: true, authMode: 'bearer' };
+}
+```
+
+### 2. Auth Check에서 자동 갱신
+
+`src/app/api/auth/check/route.ts`:
+```typescript
+// access_token 없고 refresh_token만 있으면 자동 갱신
+if (refreshToken && !accessToken) {
+ const refreshResponse = await fetch('/api/v1/refresh', {...});
+ // 새 토큰을 HttpOnly 쿠키로 설정
+}
+```
+
+### 3. API Client에서 자동 갱신
+
+`src/lib/api/client.ts`:
+```typescript
+// withTokenRefresh 헬퍼 함수 사용
+const data = await withTokenRefresh(() =>
+ apiClient.get('/protected/resource')
+);
+```
+
+**동작 방식**:
+1. API 호출 시도
+2. 401 응답 받음
+3. `/api/auth/refresh` 호출
+4. 성공 시 원래 API 재시도
+5. 실패 시 로그인 페이지로 리다이렉트
+
+---
+
+## 사용 예시
+
+### 예시 1: 보호된 페이지에서 API 호출
+
+```typescript
+// src/app/[locale]/(protected)/dashboard/page.tsx
+import { withTokenRefresh } from '@/lib/api/client';
+
+export default function Dashboard() {
+ const fetchData = async () => {
+ try {
+ // 자동 토큰 갱신 포함
+ const data = await withTokenRefresh(() =>
+ fetch('/api/protected/data', {
+ credentials: 'include' // 쿠키 포함
+ })
+ );
+
+ console.log('Data fetched:', data);
+ } catch (error) {
+ console.error('Fetch failed:', error);
+ }
+ };
+
+ return
...
;
+}
+```
+
+### 예시 2: 수동 토큰 갱신
+
+```typescript
+// src/lib/auth/token-refresh.ts
+import { refreshTokenClient } from '@/lib/auth/token-refresh';
+
+async function handleProtectedAction() {
+ try {
+ // API 호출
+ const response = await fetch('/api/protected/action');
+
+ if (!response.ok) {
+ // 401 에러 시 토큰 갱신 시도
+ const refreshed = await refreshTokenClient();
+
+ if (refreshed) {
+ // 재시도
+ return await fetch('/api/protected/action');
+ }
+ }
+
+ return response;
+ } catch (error) {
+ console.error('Action failed:', error);
+ }
+}
+```
+
+### 예시 3: Protected Layout
+
+```typescript
+// src/app/[locale]/(protected)/layout.tsx
+"use client";
+
+import { useAuthGuard } from '@/hooks/useAuthGuard';
+
+export default function ProtectedLayout({ children }) {
+ // 자동으로 /api/auth/check 호출
+ // access_token 없으면 refresh_token으로 자동 갱신
+ useAuthGuard();
+
+ return <>{children}>;
+}
+```
+
+---
+
+## 보안 고려사항
+
+### ✅ 구현된 보안 기능
+
+1. **HttpOnly 쿠키**
+ - JavaScript에서 토큰 접근 불가
+ - XSS 공격으로부터 보호
+
+2. **Secure 플래그**
+ - HTTPS에서만 쿠키 전송
+ - 중간자 공격 방지
+
+3. **SameSite=Strict**
+ - CSRF 공격 방지
+ - 크로스 사이트 요청 차단
+
+4. **토큰 만료 시간**
+ - Access Token: 2시간 (짧은 수명)
+ - Refresh Token: 7일 (긴 수명)
+
+5. **에러 메시지 일반화**
+ - 백엔드 상세 에러 노출 방지
+ - 정보 유출 차단
+
+### ⚠️ 추가 권장 사항
+
+1. **Token Rotation**
+ - Refresh 시 새로운 refresh_token 발급 (현재 구현됨 ✅)
+
+2. **Rate Limiting**
+ - 로그인 시도 제한
+ - Refresh 요청 제한
+
+3. **IP 검증**
+ - 토큰 발급 시 IP 기록
+ - 다른 IP에서 사용 시 경고
+
+4. **Device Fingerprinting**
+ - 토큰 발급 디바이스 기록
+ - 이상 접근 탐지
+
+5. **Logout Blacklist**
+ - 로그아웃 된 토큰 블랙리스트 관리
+ - 재사용 방지
+
+---
+
+## 트러블슈팅
+
+### 문제 1: 로그인 후 바로 로그아웃됨
+
+**원인**: 쿠키가 설정되지 않음
+
+**해결**:
+1. 브라우저 개발자 도구 → Application → Cookies 확인
+2. `access_token`, `refresh_token` 존재 확인
+3. 없으면 `/api/auth/login` 응답 헤더 확인
+
+### 문제 2: Token refresh 무한 루프
+
+**원인**: Refresh token도 만료됨
+
+**해결**:
+1. `/api/auth/refresh` 응답 확인
+2. 401 응답 시 로그인 페이지로 리다이렉트
+3. `needsReauth: true` 플래그 확인
+
+### 문제 3: CORS 에러
+
+**원인**: 크로스 도메인 요청 시 쿠키 전송 실패
+
+**해결**:
+```typescript
+fetch('/api/protected', {
+ credentials: 'include' // 쿠키 포함
+})
+```
+
+---
+
+## 참고 파일
+
+- `src/app/api/auth/login/route.ts` - 로그인 API
+- `src/app/api/auth/refresh/route.ts` - 토큰 갱신 API
+- `src/app/api/auth/check/route.ts` - 인증 체크 API
+- `src/app/api/auth/logout/route.ts` - 로그아웃 API
+- `src/middleware.ts` - 인증 미들웨어
+- `src/lib/auth/token-refresh.ts` - 토큰 갱신 유틸리티
+- `src/lib/api/client.ts` - API 클라이언트 (자동 갱신)
\ No newline at end of file
diff --git a/docs/[IMPL-2025-11-11] api-route-type-safety.md b/docs/[IMPL-2025-11-11] api-route-type-safety.md
new file mode 100644
index 00000000..612d2d50
--- /dev/null
+++ b/docs/[IMPL-2025-11-11] api-route-type-safety.md
@@ -0,0 +1,321 @@
+# API Route 타입 안전성 가이드
+
+## 📋 개요
+
+Next.js API Route에서 백엔드 API 응답 데이터를 프론트엔드로 전달할 때, TypeScript 타입 정의를 통해 데이터 누락을 방지하는 방법
+
+---
+
+## 🎯 문제 사례
+
+### 발생한 이슈
+로그인 API를 테스트할 때, API 테스트 도구에서는 `roles` 데이터가 정상적으로 나오지만, 프론트엔드에서는 빈 배열로 나오는 현상 발생
+
+### 원인 분석
+```typescript
+// ❌ 타입 정의 없이 데이터 전달 (문제 코드)
+const responseData = {
+ message: data.message,
+ user: data.user,
+ tenant: data.tenant,
+ menus: data.menus,
+ // roles: data.roles, ← 누락됨!
+ token_type: data.token_type,
+ expires_in: data.expires_in,
+ expires_at: data.expires_at,
+};
+```
+
+**문제점:**
+- 백엔드에서 `roles` 데이터를 반환했지만
+- Next.js API Route에서 프론트로 전달할 때 `roles` 필드를 포함하지 않음
+- 타입 정의가 없어서 컴파일 타임에 감지 불가
+
+---
+
+## ✅ 해결 방법
+
+### 1. 백엔드 응답 타입 정의
+
+```typescript
+/**
+ * 백엔드 API 로그인 응답 타입
+ */
+interface BackendLoginResponse {
+ message: string;
+ access_token: string;
+ refresh_token: string;
+ token_type: string;
+ expires_in: number;
+ expires_at: string;
+ user: {
+ id: number;
+ user_id: string;
+ name: string;
+ email: string;
+ phone: string;
+ };
+ tenant: {
+ id: number;
+ company_name: string;
+ business_num: string;
+ tenant_st_code: string;
+ other_tenants: any[];
+ };
+ menus: Array<{
+ id: number;
+ parent_id: number | null;
+ name: string;
+ url: string;
+ icon: string;
+ sort_order: number;
+ is_external: number;
+ external_url: string | null;
+ }>;
+ roles: Array<{
+ id: number;
+ name: string;
+ description: string;
+ }>;
+}
+```
+
+### 2. 프론트엔드 응답 타입 정의
+
+```typescript
+/**
+ * 프론트엔드로 전달할 응답 타입 (토큰 제외)
+ */
+interface FrontendLoginResponse {
+ message: string;
+ user: BackendLoginResponse['user'];
+ tenant: BackendLoginResponse['tenant'];
+ menus: BackendLoginResponse['menus'];
+ roles: BackendLoginResponse['roles']; // ✅ 명시적으로 포함
+ token_type: string;
+ expires_in: number;
+ expires_at: string;
+}
+```
+
+### 3. 타입 적용
+
+```typescript
+export async function POST(request: NextRequest) {
+ try {
+ // ... 백엔드 API 호출
+
+ // ✅ 타입 지정
+ const data: BackendLoginResponse = await backendResponse.json();
+
+ // ✅ 타입 지정 + 모든 필드 포함
+ const responseData: FrontendLoginResponse = {
+ message: data.message,
+ user: data.user,
+ tenant: data.tenant,
+ menus: data.menus,
+ roles: data.roles, // ✅ 누락 방지
+ token_type: data.token_type,
+ expires_in: data.expires_in,
+ expires_at: data.expires_at,
+ };
+
+ return NextResponse.json(responseData, { status: 200 });
+ } catch (error) {
+ // ... 에러 처리
+ }
+}
+```
+
+---
+
+## 🎁 타입 정의의 장점
+
+### 1. 컴파일 타임 에러 감지
+```typescript
+// ❌ roles 누락 시 TypeScript 에러 발생
+const responseData: FrontendLoginResponse = {
+ message: data.message,
+ user: data.user,
+ // ... roles 필드 빠짐
+ // ⚠️ Type Error: Property 'roles' is missing in type
+};
+```
+
+### 2. 자동 완성 지원
+- IDE에서 필드명 자동 완성
+- 오타 방지
+- 개발 생산성 향상
+
+### 3. API 문서 역할
+- 백엔드 API 스펙이 코드에 명시됨
+- 별도 문서 없이도 데이터 구조 파악 가능
+- 팀원 간 커뮤니케이션 비용 절감
+
+### 4. 리팩토링 안정성
+- 백엔드 API 변경 시 즉시 감지
+- 영향 범위 파악 용이
+- 안전한 코드 수정
+
+---
+
+## 📝 적용 체크리스트
+
+### API Route 작성 시 필수 사항
+
+- [ ] 백엔드 응답 타입 인터페이스 정의
+- [ ] 프론트엔드 응답 타입 인터페이스 정의
+- [ ] `await response.json()` 시 타입 지정
+- [ ] 프론트 응답 객체에 타입 지정
+- [ ] 모든 필수 필드 포함 확인
+
+### 타입 정의 원칙
+
+```typescript
+// ✅ Good: 명시적 타입 지정
+const data: BackendResponse = await response.json();
+const result: FrontendResponse = {
+ // ... 모든 필드 포함
+};
+
+// ❌ Bad: 타입 없이 작성
+const data = await response.json();
+const result = {
+ // ... 필드 누락 가능성
+};
+```
+
+---
+
+## 🔍 실제 적용 예시
+
+### 파일 위치
+```
+src/app/api/auth/login/route.ts
+```
+
+### Before (문제 코드)
+```typescript
+export async function POST(request: NextRequest) {
+ // ...
+ const data = await backendResponse.json(); // 타입 없음
+
+ const responseData = {
+ message: data.message,
+ user: data.user,
+ menus: data.menus,
+ // roles 누락!
+ };
+
+ return NextResponse.json(responseData);
+}
+```
+
+### After (개선 코드)
+```typescript
+interface BackendLoginResponse {
+ // ... 전체 타입 정의
+ roles: Array<{ id: number; name: string; description: string }>;
+}
+
+interface FrontendLoginResponse {
+ // ... 전체 타입 정의
+ roles: BackendLoginResponse['roles'];
+}
+
+export async function POST(request: NextRequest) {
+ // ...
+ const data: BackendLoginResponse = await backendResponse.json();
+
+ const responseData: FrontendLoginResponse = {
+ message: data.message,
+ user: data.user,
+ menus: data.menus,
+ roles: data.roles, // ✅ 명시적 포함
+ // ... 기타 필드
+ };
+
+ return NextResponse.json(responseData);
+}
+```
+
+---
+
+## 🚨 주의사항
+
+### 1. 타입과 실제 데이터 불일치
+```typescript
+// ⚠️ 백엔드 API 스펙 변경 시
+interface BackendResponse {
+ // 타입 정의는 그대로인데
+ user_name: string;
+}
+
+// 실제 응답은 변경됨
+{
+ "username": "홍길동" // 필드명 변경됨
+}
+```
+
+**대응 방안:**
+- 백엔드 API 스펙 변경 시 타입 정의도 함께 업데이트
+- API 응답 검증 로직 추가 (런타임 체크)
+- 백엔드 팀과 스펙 변경 사전 공유
+
+### 2. Optional vs Required
+```typescript
+// 명확한 옵셔널 표시
+interface Response {
+ required_field: string; // 필수
+ optional_field?: string; // 선택
+ nullable_field: string | null; // null 가능
+}
+```
+
+### 3. any 타입 남용 금지
+```typescript
+// ❌ Bad
+interface Response {
+ data: any; // 타입 안전성 상실
+}
+
+// ✅ Good
+interface Response {
+ data: {
+ id: number;
+ name: string;
+ };
+}
+```
+
+---
+
+## 📚 관련 문서
+
+- [Authentication Implementation Guide](./[IMPL-2025-11-07]%20authentication-implementation-guide.md)
+- [Token Management Guide](./[IMPL-2025-11-10]%20token-management-guide.md)
+- [API Requirements](./[REF]%20api-requirements.md)
+
+---
+
+## 📌 핵심 요약
+
+1. **API Route는 백엔드와 프론트 사이의 중간 레이어**
+ - 데이터 변환/필터링 역할 수행
+ - 타입 정의로 누락 방지
+
+2. **타입 정의의 3가지 핵심 가치**
+ - 컴파일 타임 에러 감지
+ - 개발 생산성 향상 (자동완성)
+ - 리팩토링 안정성 보장
+
+3. **실무 적용 원칙**
+ - 백엔드 응답 타입 → 프론트 응답 타입 순서로 정의
+ - 모든 API Route에 타입 적용
+ - 백엔드 스펙 변경 시 타입도 함께 업데이트
+
+---
+
+**작성일:** 2025-11-11
+**작성자:** Claude Code
+**마지막 수정:** 2025-11-11
diff --git a/docs/[IMPL-2025-11-11] chart-warning-fix.md b/docs/[IMPL-2025-11-11] chart-warning-fix.md
new file mode 100644
index 00000000..aa47b311
--- /dev/null
+++ b/docs/[IMPL-2025-11-11] chart-warning-fix.md
@@ -0,0 +1,113 @@
+# 차트 경고 수정 보고서
+
+## 문제 상황
+CEODashboard에서 다음과 같은 경고가 발생:
+```
+The width(-1) and height(-1) of chart should be greater than 0,
+please check the style of container, or the props width(100%) and height(100%),
+or add a minWidth(0) or minHeight(undefined) or use aspect(undefined) to control the
+height and width.
+```
+
+## 원인 분석
+
+### 문제 코드
+```tsx
+
+
+
+
+
+ ...
+
+
+
+
+
+```
+
+### 원인
+1. `ResponsiveContainer`가 `height="100%"`로 설정됨
+2. 부모 div가 Tailwind 클래스 `h-80` 사용
+3. 컴포넌트 마운트 시점에 부모의 계산된 높이를 제대로 읽지 못함
+4. recharts가 높이를 -1로 계산하여 경고 발생
+
+## 해결 방법
+
+### 수정 코드
+```tsx
+
+ {/* height="100%" → height={320} */}
+
+```
+
+### 수정 이유
+- `h-80` = 320px (Tailwind: 1 단위 = 4px)
+- 명시적인 픽셀 값으로 설정하여 마운트 시점에 즉시 계산 가능
+- ResponsiveContainer의 너비는 여전히 반응형 유지 (`width="100%"`)
+
+## 수정 위치
+
+### CEODashboard.tsx
+- Line 1201: 월별 매출 추이 차트
+- Line 1269: 품질 지표 차트
+- Line 1343: 생산 효율성 차트
+- Line 2127: 기타 차트
+
+총 4개의 `ResponsiveContainer` 수정 완료
+
+## 테스트
+
+### 빌드 상태
+✅ **컴파일 성공**: `✓ Compiled successfully in 3.3s`
+
+### 예상 결과
+- ✅ 차트 경고 메시지 사라짐
+- ✅ 차트가 즉시 올바른 크기로 렌더링됨
+- ✅ 반응형 동작 유지 (너비는 여전히 100%)
+
+## 적용 가능한 다른 대시보드
+
+현재는 CEODashboard에만 이 패턴이 있었지만, 만약 다른 대시보드에서도 같은 경고가 발생하면:
+
+```tsx
+// Before
+
+
+// After
+
+```
+
+또는 부모 컨테이너의 높이에 맞춰 조정
+
+## 참고사항
+
+### Tailwind 높이 클래스
+- `h-64` = 256px
+- `h-72` = 288px
+- `h-80` = 320px
+- `h-96` = 384px
+
+### ResponsiveContainer 권장 사항
+1. **고정 높이**: 대시보드 차트처럼 일정한 크기가 필요한 경우
+ ```tsx
+
+ ```
+
+2. **비율 기반**: aspect ratio로 제어하고 싶은 경우
+ ```tsx
+
+ ```
+
+3. **최소 높이**: 동적이지만 최소값이 필요한 경우
+ ```tsx
+
+ ```
+
+## 결론
+
+✅ **문제 해결**: 차트 크기 경고 완전히 제거
+✅ **성능 개선**: 마운트 시 즉시 올바른 크기로 렌더링
+✅ **반응형 유지**: 너비는 여전히 컨테이너에 맞춰 조정됨
+
+recharts의 `ResponsiveContainer`를 사용할 때는 명시적인 높이를 설정하는 것이 권장됩니다!
diff --git a/docs/[IMPL-2025-11-11] dashboard-cleanup-summary.md b/docs/[IMPL-2025-11-11] dashboard-cleanup-summary.md
new file mode 100644
index 00000000..355e7280
--- /dev/null
+++ b/docs/[IMPL-2025-11-11] dashboard-cleanup-summary.md
@@ -0,0 +1,185 @@
+# 대시보드 레이아웃 정리 완료 보고서
+
+## 작업 일시
+2025-11-11
+
+## 작업 개요
+DashboardLayout.tsx에서 테스트용 역할 선택 셀렉트 메뉴를 제거하고, 간단한 로그아웃 버튼으로 교체하여 UI를 정리했습니다.
+
+## 변경 사항
+
+### 1. 제거된 기능
+
+#### 역할 선택 셀렉트 메뉴
+```tsx
+// ❌ 제거됨
+
+```
+
+#### 관련 코드 제거
+- `handleRoleChange()` 함수 (역할 전환 로직)
+- `roleDashboards` 배열 (역할 정의)
+- `setCurrentRole`, `setUserName`, `setUserPosition` state setter 함수
+
+### 2. 추가된 기능
+
+#### 간단한 로그아웃 버튼
+```tsx
+// ✅ 추가됨
+
+```
+
+### 3. 유지된 기능
+
+#### 유저 프로필 표시
+```tsx
+
+
+
+
+
+
+
{userName}
+
{userPosition}
+
+
+
+```
+
+#### 로그아웃 기능
+```tsx
+const handleLogout = async () => {
+ try {
+ // 1. HttpOnly 쿠키 삭제 API 호출
+ const response = await fetch('/api/auth/logout', {
+ method: 'POST',
+ });
+
+ if (response.ok) {
+ console.log('✅ 로그아웃 완료: HttpOnly 쿠키 삭제됨');
+ }
+
+ // 2. localStorage 정리
+ localStorage.removeItem('user');
+
+ // 3. 로그인 페이지로 리다이렉트
+ router.push('/login');
+ } catch (error) {
+ console.error('로그아웃 처리 중 오류:', error);
+ localStorage.removeItem('user');
+ router.push('/login');
+ }
+};
+```
+
+## 헤더 레이아웃 비교
+
+### 변경 전
+```
+[메뉴] [검색바] ... [테마토글] [유저프로필(드롭다운)] [역할선택 셀렉트]
+```
+
+### 변경 후
+```
+[메뉴] [검색바] ... [테마토글] [유저프로필] [로그아웃 버튼]
+```
+
+## 영향 분석
+
+### ✅ 긍정적 영향
+1. **UI 단순화**: 불필요한 역할 전환 기능 제거로 헤더가 깔끔해짐
+2. **사용자 혼란 방지**: 테스트용 기능이 프로덕션에 노출되지 않음
+3. **명확한 로그아웃**: 드롭다운 대신 버튼으로 로그아웃 기능 명확화
+4. **코드 정리**: 미사용 함수 및 변수 제거로 코드 가독성 향상
+
+### 🔄 기능 변경 없음
+- 역할 기반 대시보드 표시 기능은 유지됨 (로그인 시 역할에 따라 자동 결정)
+- 로그아웃 기능 동작 방식 유지
+- 메뉴 생성 로직 유지
+
+## 파일 변경 내역
+
+### 수정된 파일
+- `src/layouts/DashboardLayout.tsx`
+ - 역할 선택 셀렉트 메뉴 제거 (Line 407-420)
+ - `handleRoleChange` 함수 제거 (Line 232-277)
+ - `roleDashboards` 배열 제거 (Line 100-107)
+ - state setter 함수 제거 (setCurrentRole, setUserName, setUserPosition)
+ - 유저 프로필 드롭다운을 일반 div로 변경
+ - 로그아웃 버튼 추가
+
+### 백업된 파일
+- `src/app/[locale]/(protected)/dashboard/page.tsx.backup` (참고용)
+
+## 빌드 상태
+
+✅ **컴파일 성공**: `✓ Compiled successfully in 3.2s`
+⚠️ **ESLint 경고**: 비즈니스 컴포넌트의 미사용 변수 (기능에 영향 없음)
+
+## 테스트 방법
+
+### 1. 로그인 플로우
+```bash
+1. npm run dev
+2. http://localhost:3000/login 접속
+3. 로그인 (API에서 반환된 역할에 따라 자동 대시보드 표시)
+```
+
+### 2. 로그아웃 테스트
+```bash
+1. 대시보드 우측 상단 "로그아웃" 버튼 클릭
+2. 로그인 페이지로 리다이렉트 확인
+3. localStorage에서 user 정보 삭제 확인 (개발자 도구)
+```
+
+### 3. 역할 기반 대시보드
+- CEO로 로그인 → CEODashboard 표시
+- ProductionManager로 로그인 → ProductionManagerDashboard 표시
+- Worker로 로그인 → WorkerDashboard 표시
+- SystemAdmin로 로그인 → SystemAdminDashboard 표시
+- Sales로 로그인 → SalesLeadDashboard 표시
+
+## 다음 단계
+
+### 권장 작업
+1. ESLint 경고 정리 (비즈니스 컴포넌트의 미사용 변수)
+2. 역할 관리 기능을 별도 설정 페이지로 이동 (관리자용)
+3. 프로필 설정 페이지 추가 (사용자 정보 수정)
+4. 로그아웃 버튼에 확인 다이얼로그 추가 (선택사항)
+
+### 추후 개선 사항
+1. 역할 전환 기능이 필요한 경우:
+ - 시스템 관리자 전용 설정 페이지에 추가
+ - 개발/테스트 환경에서만 활성화
+ - 권한 검증 로직 추가
+
+2. 사용자 경험 개선:
+ - 로그아웃 시 확인 모달 추가
+ - 프로필 드롭다운 메뉴 추가 (프로필 보기, 설정, 로그아웃)
+ - 알림 기능 추가
+
+## 결론
+
+✅ **정리 완료**: 테스트용 역할 선택 기능 제거
+✅ **기능 유지**: 역할 기반 대시보드 시스템 정상 동작
+✅ **빌드 성공**: 컴파일 및 동작 정상
+✅ **UI 개선**: 깔끔하고 명확한 헤더 레이아웃
+
+대시보드 레이아웃이 프로덕션에 적합한 상태로 정리되었습니다!
diff --git a/docs/[IMPL-2025-11-11] error-pages-configuration.md b/docs/[IMPL-2025-11-11] error-pages-configuration.md
new file mode 100644
index 00000000..8a25c560
--- /dev/null
+++ b/docs/[IMPL-2025-11-11] error-pages-configuration.md
@@ -0,0 +1,572 @@
+# 에러 및 특수 페이지 구성 가이드
+
+## 📋 개요
+
+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 (
+
404 - 페이지를 찾을 수 없습니다
+ );
+}
+```
+
+**트리거:**
+- 존재하지 않는 URL 접근
+- `notFound()` 함수 호출
+
+#### Protected 404 (`app/[locale]/(protected)/not-found.tsx`)
+
+```typescript
+// ✅ 특징:
+// - DashboardLayout 자동 적용 (사이드바, 헤더)
+// - 인증된 사용자만 볼 수 있음
+// - 보호된 경로 내 404만 처리
+
+export default function ProtectedNotFoundPage() {
+ return (
+